Bring piston-api up to current master

Conflicts:

	.gitignore
	urls.py
This commit is contained in:
2010-04-14 12:55:18 +01:00
197 changed files with 9636 additions and 6559 deletions

4
.gitignore vendored
View File

@@ -4,6 +4,6 @@
django/ django/
registration/ registration/
dbsettings.py dbsettings.py
django_cron
django_evolution
piston piston
django_evolution
logs/

7
cronjobs.txt Normal file
View File

@@ -0,0 +1,7 @@
ROOT=/home/matalok/auth/auth
@daily $ROOT/run-cron.py reddit.cron UpdateAPIs > $ROOT/logs/redditapi-update.log 2>&1
*/10 * * * * $ROOT/run-cron.py reddit.cron ProcessValidations > $ROOT/logs/reddit-validations.log 2>&1
*/5 * * * * $ROOT/run-cron.py eve_api.cron UpdateAPIs > $ROOT/logs/eveapi-update.log 2>&1
@hourly $ROOT/run-cron.py sso.cron RemoveInvalidUsers > $ROOT/logs/auth-update.log 2>&1
@daily $ROOT/run-cron.py registration.cron RemoveExpiredProfiles > /dev/null 2>&1

View File

@@ -1,35 +0,0 @@
K 25
svn:wc:ra_dav:version-url
V 34
/svn/!svn/ver/32/trunk/django_cron
END
base.py
K 25
svn:wc:ra_dav:version-url
V 42
/svn/!svn/ver/32/trunk/django_cron/base.py
END
__init__.py
K 25
svn:wc:ra_dav:version-url
V 46
/svn/!svn/ver/16/trunk/django_cron/__init__.py
END
signals.py
K 25
svn:wc:ra_dav:version-url
V 44
/svn/!svn/ver/8/trunk/django_cron/signals.py
END
models.py
K 25
svn:wc:ra_dav:version-url
V 44
/svn/!svn/ver/16/trunk/django_cron/models.py
END
README.txt
K 25
svn:wc:ra_dav:version-url
V 45
/svn/!svn/ver/16/trunk/django_cron/README.txt
END

View File

@@ -1,198 +0,0 @@
9
dir
32
http://django-cron.googlecode.com/svn/trunk/django_cron
http://django-cron.googlecode.com/svn
2009-10-28T14:21:55.477932Z
32
jim.mixtake@gmail.com
svn:special svn:externals svn:needs-lock
857153d3-df44-0410-aaef-ab1f86cdcd94
base.py
file
2010-02-20T20:20:49.000000Z
afab29654c6380b9c2ecd00fae92522c
2009-10-28T14:21:55.477932Z
32
jim.mixtake@gmail.com
4064
__init__.py
file
2010-02-20T20:20:49.000000Z
8492cf50017e7eff784fdfbe0cc8b3ba
2009-06-02T13:59:46.682354Z
16
Jim.mixtake
2566
signals.py
file
2010-02-20T20:20:49.000000Z
8e3eba63f720bd6d830fc89d7d6be693
2008-10-26T12:02:48.705921Z
5
digitalxero
1192
models.py
file
2010-02-20T20:20:49.000000Z
329f05f78f78465d4acfba95c42d32f7
2009-06-02T13:59:46.682354Z
16
Jim.mixtake
1632
README.txt
file
2010-02-20T20:20:49.000000Z
801836776103697e20d5d9a9446cb28b
2009-06-02T13:59:46.682354Z
16
Jim.mixtake
1953

View File

@@ -1 +0,0 @@
9

View File

@@ -1,54 +0,0 @@
= How to install djang-cron =
1. Put 'django_cron' into your python path
2. Add 'django_cron' to INSTALLED_APPS in your settings.py file
3. Add the following code to the beginning of your urls.py file (just after the imports):
import django_cron
django_cron.autodiscover()
4. Create a file called 'cron.py' inside each installed app that you want to add a recurring job to. The app must be installed via the INSTALLED_APPS in your settings.py or the autodiscover will not find it.
=== Important note ===
If you are using mod_python, you need to make sure your server is set up to server more than one request per instance, Otherwise it will kill django-cron before the tasks get started. The specific line to look for is in your 'httpd.conf' file:
# THIS IS BAD!!! IT WILL CRIPPLE DJANGO-CRON
MaxRequestsPerChild 1
Change it to a value that is large enough that your cron jobs will get run at least once per instance. We're working on resolving this issue without dictating your server config.
In the meantime, django_cron is best used to execute tasks that occur relatively often (at least once an hour). Try setting MaxRequestsPerChild to 50, 100, or 200
# Depending on traffic, and your server config, a number between 50 and 500 is probably good
# Note: the higher this number, the more memory django is likely to use. Be careful on shared hosting
MaxRequestsPerChild 100
== Example cron.py ==
from django_cron import cronScheduler, Job
# This is a function I wrote to check a feedback email address
# and add it to our database. Replace with your own imports
from MyMailFunctions import check_feedback_mailbox
class CheckMail(Job):
"""
Cron Job that checks the lgr users mailbox and adds any
approved senders' attachments to the db
"""
# run every 300 seconds (5 minutes)
run_every = 300
def job(self):
# This will be executed every 5 minutes
check_feedback_mailbox()
cronScheduler.register(CheckMail)

View File

@@ -1,63 +0,0 @@
"""
Copyright (c) 2007-2008, Dj Gilcrease
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from base import Job, cronScheduler
def autodiscover():
"""
Auto-discover INSTALLED_APPS cron.py modules and fail silently when
not present. This forces an import on them to register any cron jobs they
may want.
"""
import imp
from django.conf import settings
for app in settings.INSTALLED_APPS:
# For each app, we need to look for an cron.py inside that app's
# package. We can't use os.path here -- recall that modules may be
# imported different ways (think zip files) -- so we need to get
# the app's __path__ and look for cron.py on that path.
# Step 1: find out the app's __path__ Import errors here will (and
# should) bubble up, but a missing __path__ (which is legal, but weird)
# fails silently -- apps that do weird things with __path__ might
# need to roll their own cron registration.
try:
app_path = __import__(app, {}, {}, [app.split('.')[-1]]).__path__
except AttributeError:
continue
# Step 2: use imp.find_module to find the app's admin.py. For some
# reason imp.find_module raises ImportError if the app can't be found
# but doesn't actually try to import the module. So skip this app if
# its admin.py doesn't exist
try:
imp.find_module('cron', app_path)
except ImportError:
continue
# Step 3: import the app's cron file. If this has errors we want them
# to bubble up.
__import__("%s.cron" % app)
# Step 4: once we find all the cron jobs, start the cronScheduler
cronScheduler.execute()

View File

@@ -1,127 +0,0 @@
"""
Copyright (c) 2007-2008, Dj Gilcrease
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import cPickle
from threading import Timer
from datetime import datetime
from django.dispatch import dispatcher
from django.conf import settings
from signals import cron_done
import models
# how often to check if jobs are ready to be run (in seconds)
# in reality if you have a multithreaded server, it may get checked
# more often that this number suggests, so keep an eye on it...
# default value: 300 seconds == 5 min
polling_frequency = getattr(settings, "CRON_POLLING_FREQUENCY", 300)
class Job(object):
# 86400 seconds == 24 hours
run_every = 86400
def run(self, *args, **kwargs):
self.job()
cron_done.send(sender=self, *args, **kwargs)
def job(self):
"""
Should be overridden (this way is cleaner, but the old way - overriding run() - will still work)
"""
pass
class CronScheduler(object):
def register(self, job_class, *args, **kwargs):
"""
Register the given Job with the scheduler class
"""
job_instance = job_class()
if not isinstance(job_instance, Job):
raise TypeError("You can only register a Job not a %r" % job_class)
job, created = models.Job.objects.get_or_create(name=str(job_instance.__class__))
if created:
job.instance = cPickle.dumps(job_instance)
job.args = cPickle.dumps(args)
job.kwargs = cPickle.dumps(kwargs)
job.run_frequency = job_instance.run_every
job.save()
def execute(self):
"""
Queue all Jobs for execution
"""
status, created = models.Cron.objects.get_or_create(pk=1)
# This is important for 2 reasons:
# 1. It keeps us for running more than one instance of the
# same job at a time
# 2. It reduces the number of polling threads because they
# get killed off if they happen to check while another
# one is already executing a job (only occurs with
# multi-threaded servers)
if status.executing:
return
status.executing = True
try:
status.save()
except:
# this will fail if you're debugging, so we want it
# to fail silently and start the timer again so we
# can pick up where we left off once debugging is done
Timer(polling_frequency, self.execute).start()
return
jobs = models.Job.objects.all()
for job in jobs:
if job.queued:
time_delta = datetime.now() - job.last_run
if (time_delta.seconds + 86400*time_delta.days) > job.run_frequency:
inst = cPickle.loads(str(job.instance))
args = cPickle.loads(str(job.args))
kwargs = cPickle.loads(str(job.kwargs))
try:
inst.run(*args, **kwargs)
job.last_run = datetime.now()
job.save()
except Exception:
# if the job throws an error, just remove it from
# the queue. That way we can find/fix the error and
# requeue the job manually
job.queued = False
job.save()
status.executing = False
status.save()
# Set up for this function to run again
Timer(polling_frequency, self.execute).start()
cronScheduler = CronScheduler()

View File

@@ -1,39 +0,0 @@
"""
Copyright (c) 2007-2008, Dj Gilcrease
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from django.db import models
from datetime import datetime
class Job(models.Model):
name = models.CharField(max_length=100)
# time between job runs (in seconds) // default: 1 day
run_frequency = models.PositiveIntegerField(default=86400)
last_run = models.DateTimeField(default=datetime.now())
instance = models.TextField()
args = models.TextField()
kwargs = models.TextField()
queued = models.BooleanField(default=True)
class Cron(models.Model):
executing = models.BooleanField(default=False)

View File

@@ -1,26 +0,0 @@
"""
Copyright (c) 2007-2008, Dj Gilcrease
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from django.dispatch import Signal
cron_queued = Signal()
cron_done = Signal(providing_args=["job"])

View File

@@ -1,54 +0,0 @@
= How to install djang-cron =
1. Put 'django_cron' into your python path
2. Add 'django_cron' to INSTALLED_APPS in your settings.py file
3. Add the following code to the beginning of your urls.py file (just after the imports):
import django_cron
django_cron.autodiscover()
4. Create a file called 'cron.py' inside each installed app that you want to add a recurring job to. The app must be installed via the INSTALLED_APPS in your settings.py or the autodiscover will not find it.
=== Important note ===
If you are using mod_python, you need to make sure your server is set up to server more than one request per instance, Otherwise it will kill django-cron before the tasks get started. The specific line to look for is in your 'httpd.conf' file:
# THIS IS BAD!!! IT WILL CRIPPLE DJANGO-CRON
MaxRequestsPerChild 1
Change it to a value that is large enough that your cron jobs will get run at least once per instance. We're working on resolving this issue without dictating your server config.
In the meantime, django_cron is best used to execute tasks that occur relatively often (at least once an hour). Try setting MaxRequestsPerChild to 50, 100, or 200
# Depending on traffic, and your server config, a number between 50 and 500 is probably good
# Note: the higher this number, the more memory django is likely to use. Be careful on shared hosting
MaxRequestsPerChild 100
== Example cron.py ==
from django_cron import cronScheduler, Job
# This is a function I wrote to check a feedback email address
# and add it to our database. Replace with your own imports
from MyMailFunctions import check_feedback_mailbox
class CheckMail(Job):
"""
Cron Job that checks the lgr users mailbox and adds any
approved senders' attachments to the db
"""
# run every 300 seconds (5 minutes)
run_every = 300
def job(self):
# This will be executed every 5 minutes
check_feedback_mailbox()
cronScheduler.register(CheckMail)

View File

@@ -1,63 +0,0 @@
"""
Copyright (c) 2007-2008, Dj Gilcrease
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from base import Job, cronScheduler
def autodiscover():
"""
Auto-discover INSTALLED_APPS cron.py modules and fail silently when
not present. This forces an import on them to register any cron jobs they
may want.
"""
import imp
from django.conf import settings
for app in settings.INSTALLED_APPS:
# For each app, we need to look for an cron.py inside that app's
# package. We can't use os.path here -- recall that modules may be
# imported different ways (think zip files) -- so we need to get
# the app's __path__ and look for cron.py on that path.
# Step 1: find out the app's __path__ Import errors here will (and
# should) bubble up, but a missing __path__ (which is legal, but weird)
# fails silently -- apps that do weird things with __path__ might
# need to roll their own cron registration.
try:
app_path = __import__(app, {}, {}, [app.split('.')[-1]]).__path__
except AttributeError:
continue
# Step 2: use imp.find_module to find the app's admin.py. For some
# reason imp.find_module raises ImportError if the app can't be found
# but doesn't actually try to import the module. So skip this app if
# its admin.py doesn't exist
try:
imp.find_module('cron', app_path)
except ImportError:
continue
# Step 3: import the app's cron file. If this has errors we want them
# to bubble up.
__import__("%s.cron" % app)
# Step 4: once we find all the cron jobs, start the cronScheduler
cronScheduler.execute()

View File

@@ -1,127 +0,0 @@
"""
Copyright (c) 2007-2008, Dj Gilcrease
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import cPickle
from threading import Timer
from datetime import datetime
from django.dispatch import dispatcher
from django.conf import settings
from signals import cron_done
import models
# how often to check if jobs are ready to be run (in seconds)
# in reality if you have a multithreaded server, it may get checked
# more often that this number suggests, so keep an eye on it...
# default value: 300 seconds == 5 min
polling_frequency = getattr(settings, "CRON_POLLING_FREQUENCY", 300)
class Job(object):
# 86400 seconds == 24 hours
run_every = 86400
def run(self, *args, **kwargs):
self.job()
cron_done.send(sender=self, *args, **kwargs)
def job(self):
"""
Should be overridden (this way is cleaner, but the old way - overriding run() - will still work)
"""
pass
class CronScheduler(object):
def register(self, job_class, *args, **kwargs):
"""
Register the given Job with the scheduler class
"""
job_instance = job_class()
if not isinstance(job_instance, Job):
raise TypeError("You can only register a Job not a %r" % job_class)
job, created = models.Job.objects.get_or_create(name=str(job_instance.__class__))
if created:
job.instance = cPickle.dumps(job_instance)
job.args = cPickle.dumps(args)
job.kwargs = cPickle.dumps(kwargs)
job.run_frequency = job_instance.run_every
job.save()
def execute(self):
"""
Queue all Jobs for execution
"""
status, created = models.Cron.objects.get_or_create(pk=1)
# This is important for 2 reasons:
# 1. It keeps us for running more than one instance of the
# same job at a time
# 2. It reduces the number of polling threads because they
# get killed off if they happen to check while another
# one is already executing a job (only occurs with
# multi-threaded servers)
if status.executing:
return
status.executing = True
try:
status.save()
except:
# this will fail if you're debugging, so we want it
# to fail silently and start the timer again so we
# can pick up where we left off once debugging is done
Timer(polling_frequency, self.execute).start()
return
jobs = models.Job.objects.all()
for job in jobs:
if job.queued:
time_delta = datetime.now() - job.last_run
if (time_delta.seconds + 86400*time_delta.days) > job.run_frequency:
inst = cPickle.loads(str(job.instance))
args = cPickle.loads(str(job.args))
kwargs = cPickle.loads(str(job.kwargs))
try:
inst.run(*args, **kwargs)
job.last_run = datetime.now()
job.save()
except Exception:
# if the job throws an error, just remove it from
# the queue. That way we can find/fix the error and
# requeue the job manually
job.queued = False
job.save()
status.executing = False
status.save()
# Set up for this function to run again
Timer(polling_frequency, self.execute).start()
cronScheduler = CronScheduler()

View File

@@ -1,39 +0,0 @@
"""
Copyright (c) 2007-2008, Dj Gilcrease
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from django.db import models
from datetime import datetime
class Job(models.Model):
name = models.CharField(max_length=100)
# time between job runs (in seconds) // default: 1 day
run_frequency = models.PositiveIntegerField(default=86400)
last_run = models.DateTimeField(default=datetime.now())
instance = models.TextField()
args = models.TextField()
kwargs = models.TextField()
queued = models.BooleanField(default=True)
class Cron(models.Model):
executing = models.BooleanField(default=False)

View File

@@ -1,26 +0,0 @@
"""
Copyright (c) 2007-2008, Dj Gilcrease
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from django.dispatch import Signal
cron_queued = Signal()
cron_done = Signal(providing_args=["job"])

View File

@@ -4,9 +4,20 @@ Admin interface models. Automatically detected by admin.autodiscover().
from django.contrib import admin from django.contrib import admin
from eve_api.models import * from eve_api.models import *
from eve_api.api_puller.accounts import import_eve_account
def account_api_update(modeladmin, request, queryset):
for obj in queryset:
import_eve_account(obj.api_key, obj.api_user_id)
account_api_update.short_description = "Update account from the EVE API"
class EVEAccountAdmin(admin.ModelAdmin): class EVEAccountAdmin(admin.ModelAdmin):
list_display = ('id', 'user', 'api_status', 'api_last_updated') list_display = ('id', 'user', 'api_status', 'api_last_updated')
search_fields = ['id'] search_fields = ['id']
actions = [account_api_update]
admin.site.register(EVEAccount, EVEAccountAdmin) admin.site.register(EVEAccount, EVEAccountAdmin)
class EVEPlayerCharacterAdmin(admin.ModelAdmin): class EVEPlayerCharacterAdmin(admin.ModelAdmin):

View File

@@ -22,22 +22,44 @@ def import_eve_account(api_key, user_id):
""" """
Imports an account from the API into the EVEAccount model. Imports an account from the API into the EVEAccount model.
""" """
print user_id, ":", api_key
auth_params = {'userID': user_id, 'apiKey': api_key} auth_params = {'userID': user_id, 'apiKey': api_key}
try:
account_doc = CachedDocument.objects.api_query('/account/Characters.xml.aspx', account_doc = CachedDocument.objects.api_query('/account/Characters.xml.aspx',
params=auth_params, params=auth_params,
no_cache=False) no_cache=False)
#print account_doc.body except APIAuthException:
try:
account = EVEAccount.objects.get(id=user_id)
except EVEAccount.DoesNotExist:
return
if api_key == account.api_key:
account.api_status = API_STATUS_AUTH_ERROR
account.api_last_updated = datetime.utcnow()
account.save()
return
except APINoUserIDException:
try:
account = EVEAccount.objects.get(id=user_id)
account.delete()
except EVEAccount.DoesNotExist:
return
dom = minidom.parseString(account_doc.body) dom = minidom.parseString(account_doc.body.encode('utf-8'))
if dom.getElementsByTagName('error'): enode = dom.getElementsByTagName('error')
if enode:
try: try:
account = EVEAccount.objects.get(id=user_id) account = EVEAccount.objects.get(id=user_id)
except EVEAccount.DoesNotExist: except EVEAccount.DoesNotExist:
return return
error = enode[0].getAttribute('code')
if error == '211':
account.api_status = API_STATUS_ACC_EXPIRED
else:
account.api_status = API_STATUS_OTHER_ERROR account.api_status = API_STATUS_OTHER_ERROR
account.api_last_updated = datetime.utcnow()
account.save() account.save()
return return
@@ -52,32 +74,83 @@ def import_eve_account(api_key, user_id):
account.api_key = api_key account.api_key = api_key
account.api_user_id = user_id account.api_user_id = user_id
account.api_last_updated = datetime.utcnow()
account.api_status = API_STATUS_OK account.api_status = API_STATUS_OK
account.save()
for node in characters_node_children: for node in characters_node_children:
try: try:
# Get this first, as it's safe. char = import_eve_character(api_key, user_id, node.getAttribute('characterID'))
corporation_id = node.getAttribute('corporationID') if char:
corp, created = EVEPlayerCorporation.objects.get_or_create(id=corporation_id) account.characters.add(char)
# Do this last, since the things we retrieved above are used
# on the EVEPlayerCharacter object's fields.
character_id = node.getAttribute('characterID')
pchar, created = EVEPlayerCharacter.objects.get_or_create(id=character_id)
name = node.getAttribute('name')
# Save these for last to keep the save count low.
pchar.name = name
pchar.corporation = corp
pchar.api_last_updated = datetime.utcnow()
pchar.save()
account.characters.add(pchar)
except AttributeError: except AttributeError:
# This must be a Text node, ignore it. # This must be a Text node, ignore it.
continue continue
account.api_last_updated = datetime.utcnow()
account.save()
return account return account
def import_eve_character(api_key, user_id, character_id):
auth_params = {'userID': user_id, 'apiKey': api_key, 'characterID': character_id }
char_doc = CachedDocument.objects.api_query('/char/CharacterSheet.xml.aspx',
params=auth_params,
no_cache=False)
dom = minidom.parseString(char_doc.body)
if dom.getElementsByTagName('error'):
return
nodes = dom.getElementsByTagName('result')[0].childNodes
pchar, created = EVEPlayerCharacter.objects.get_or_create(id=character_id)
values = {}
for node in nodes:
if node.nodeType == 1:
node.normalize()
if len(node.childNodes) == 1:
values[node.tagName] = node.childNodes[0].nodeValue
else:
if node.tagName == "rowset":
continue
nv = {}
for nd in node.childNodes:
if nd.nodeType == 1:
nv[nd.tagName] = nd.childNodes[0].nodeValue
values[node.tagName] = nv
# Get this first, as it's safe.
corporation_id = values['corporationID']
corp, created = EVEPlayerCorporation.objects.get_or_create(id=corporation_id)
if not corp.name:
try:
corp.query_and_update_corp()
except:
pass
name = values['name']
# Save these for last to keep the save count low.
pchar.name = name
pchar.corporation = corp
pchar.balance = values['balance']
pchar.attrib_intelligence = values['attributes']['intelligence']
pchar.attrib_charisma = values['attributes']['charisma']
pchar.attrib_perception = values['attributes']['perception']
pchar.attrib_willpower = values['attributes']['willpower']
pchar.attrib_memory = values['attributes']['memory']
if values['gender'] == 'Male':
pchar.gender = 1
else:
pchar.gender = 2
pchar.race = API_RACES[values['race']]
pchar.api_last_updated = datetime.utcnow()
pchar.save()
return pchar
if __name__ == "__main__": if __name__ == "__main__":
""" """
Test import. Test import.

View File

@@ -6,10 +6,62 @@ API_STATUS_PENDING = 0
API_STATUS_OK = 1 API_STATUS_OK = 1
API_STATUS_AUTH_ERROR = 2 API_STATUS_AUTH_ERROR = 2
API_STATUS_OTHER_ERROR = 3 API_STATUS_OTHER_ERROR = 3
API_STATUS_ACC_EXPIRED = 4
# This tuple is used to assemble the choices list for the field. # This tuple is used to assemble the choices list for the field.
API_STATUS_CHOICES = ( API_STATUS_CHOICES = (
(API_STATUS_PENDING, 'Unknown'), (API_STATUS_PENDING, 'Unknown'),
(API_STATUS_OK, 'OK'), (API_STATUS_OK, 'OK'),
(API_STATUS_AUTH_ERROR, 'Auth Error'), (API_STATUS_AUTH_ERROR, 'Auth Error'),
(API_STATUS_OTHER_ERROR, 'Other Error'), (API_STATUS_OTHER_ERROR, 'Other Error'),
(API_STATUS_ACC_EXPIRED, 'Account Expired'),
) )
API_GENDER_CHOICES = (
(1, 'Male'),
(2, 'Female'),
)
API_RACES_CHOICES = (
(1, 'Caldari'),
(2, 'Minmatar'),
(3, 'Gallente'),
(4, 'Amarr'),
)
API_BLOODLINES_CHOICES = (
(1, 'Sebiestor'),
(2, 'Vherokior'),
(3, 'Brutor'),
(4, 'Intaki'),
(5, 'Gallente'),
(6, 'Jin-Mei'),
(7, 'Civire'),
(8, 'Deteis'),
(9, 'Achura'),
(10, 'Amarr'),
(11, 'Khanid'),
(12, 'Ni-Kunni'),
(13, 'Caldari'),
)
API_RACES = {
'Caldari': 1,
'Minmatar': 2,
'Gallente': 3,
'Amarr': 4,
}
API_BLOODLINES = {
'Sebiestor': 1,
'Vherokior': 2,
'Brutor': 3,
'Intaki': 4,
'Gallente': 5,
'Jin-Mei': 6,
'Civire': 7,
'Deteis': 8,
'Achura': 9,
'Amarr': 10,
'Khanid': 11,
'Ni-Kunni': 12,
}

View File

@@ -1,17 +1,19 @@
import logging import logging
import datetime
from django_cron import cronScheduler, Job
from eve_api.models.api_player import EVEAccount, EVEPlayerCorporation from eve_api.models.api_player import EVEAccount, EVEPlayerCorporation
import eve_api.api_puller.accounts import eve_api.api_puller.accounts
from eve_api.api_exceptions import APIAuthException, APINoUserIDException from eve_api.api_exceptions import APIAuthException, APINoUserIDException
class UpdateAPIs(Job): class UpdateAPIs():
""" """
Updates all Eve API elements in the database Updates all Eve API elements in the database
""" """
# run every 2 hours settings = { 'update_corp': False }
run_every = 7200
last_update_delay = 86400
batches = 50
@property @property
def _logger(self): def _logger(self):
@@ -21,21 +23,23 @@ class UpdateAPIs(Job):
def job(self): def job(self):
# Update all the eve accounts and related corps # Update all the eve accounts and related corps
for acc in EVEAccount.objects.all():
delta = datetime.timedelta(seconds=self.last_update_delay)
self._logger.debug("Updating APIs older than %s" % (datetime.datetime.now() - delta))
accounts = EVEAccount.objects.filter(api_last_updated__lt=(datetime.datetime.now() - delta))[:self.batches]
self._logger.debug("%s account(s) to update" % len(accounts))
for acc in accounts:
self._logger.info("Updating UserID %s" % acc.api_user_id) self._logger.info("Updating UserID %s" % acc.api_user_id)
if not acc.user: if not acc.user:
acc.delete() acc.delete()
continue continue
try:
eve_api.api_puller.accounts.import_eve_account(acc.api_key, acc.api_user_id) eve_api.api_puller.accounts.import_eve_account(acc.api_key, acc.api_user_id)
acc.api_status = 1
except APIAuthException:
acc.api_status = 2
acc.save()
if self.settings['update_corp']:
for corp in EVEPlayerCorporation.objects.all(): for corp in EVEPlayerCorporation.objects.all():
try:
corp.query_and_update_corp() corp.query_and_update_corp()
except:
self._logger.error('Error updating %s' % corp)
cronScheduler.register(UpdateAPIs) continue

View File

@@ -0,0 +1 @@
SEQUENCE = ['applications-field']

View File

@@ -0,0 +1,7 @@
from django_evolution.mutations import *
from django.db import models
MUTATIONS = [
AddField('EVEPlayerCorporation', 'applications', models.BooleanField, initial=False)
]

View File

@@ -79,10 +79,9 @@ class EVEPlayerCorporationManager(models.Manager):
""" """
corp_doc = CachedDocument.objects.api_query('/corp/CorporationSheet.xml.aspx', corp_doc = CachedDocument.objects.api_query('/corp/CorporationSheet.xml.aspx',
params={'corporationID': id}) params={'corporationID': id})
corp_dat = corp_doc.body.decode("utf-8", "replace")
# Convert incoming data to UTF-8. # Convert incoming data to UTF-8.
dom = minidom.parseString(corp_dat) dom = minidom.parseString(corp_doc.body.encode('utf-8'))
error_node = dom.getElementsByTagName('error') error_node = dom.getElementsByTagName('error')

View File

@@ -7,7 +7,7 @@ from django.db import models
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from eve_proxy.models import CachedDocument from eve_proxy.models import CachedDocument
from eve_api.managers import EVEPlayerCorporationManager, EVEPlayerAllianceManager, EVEPlayerCharacterManager from eve_api.managers import EVEPlayerCorporationManager, EVEPlayerAllianceManager, EVEPlayerCharacterManager
from eve_api.app_defines import API_STATUS_CHOICES, API_STATUS_PENDING from eve_api.app_defines import API_STATUS_CHOICES, API_STATUS_PENDING, API_RACES_CHOICES, API_GENDER_CHOICES
class EVEAPIModel(models.Model): class EVEAPIModel(models.Model):
""" """
@@ -39,6 +39,12 @@ class EVEAccount(EVEAPIModel):
verbose_name="API Status", verbose_name="API Status",
help_text="End result of the last attempt at updating this object from the API.") help_text="End result of the last attempt at updating this object from the API.")
@property
def api_status_description(self):
for choice in API_STATUS_CHOICES:
if choice[0] == self.api_status:
return choice[1]
def in_corp(self, corpid): def in_corp(self, corpid):
for char in self.characters.all(): for char in self.characters.all():
if char.corporation_id == corpid: if char.corporation_id == corpid:
@@ -58,10 +64,8 @@ class EVEPlayerCharacter(EVEAPIModel):
""" """
name = models.CharField(max_length=255, blank=True, null=False) name = models.CharField(max_length=255, blank=True, null=False)
corporation = models.ForeignKey('EVEPlayerCorporation', blank=True, null=True) corporation = models.ForeignKey('EVEPlayerCorporation', blank=True, null=True)
# TODO: Choices field race = models.IntegerField(blank=True, null=True, choices=API_RACES_CHOICES)
race = models.IntegerField(blank=True, null=True) gender = models.IntegerField(blank=True, null=True, choices=API_GENDER_CHOICES)
# TODO: Choices field
gender = models.IntegerField(blank=True, null=True)
balance = models.FloatField("Account Balance", blank=True, null=True) balance = models.FloatField("Account Balance", blank=True, null=True)
attrib_intelligence = models.IntegerField("Intelligence", blank=True, attrib_intelligence = models.IntegerField("Intelligence", blank=True,
null=True) null=True)
@@ -72,6 +76,18 @@ class EVEPlayerCharacter(EVEAPIModel):
objects = EVEPlayerCharacterManager() objects = EVEPlayerCharacterManager()
@property
def gender_description(self):
for choice in API_GENDER_CHOICES:
if choice[0] == self.gender:
return choice[1]
@property
def race_description(self):
for choice in API_RACES_CHOICES:
if choice[0] == self.race:
return choice[1]
def __unicode__(self): def __unicode__(self):
if self.name: if self.name:
return "%s (%d)" % (self.name, self.id) return "%s (%d)" % (self.name, self.id)
@@ -141,6 +157,7 @@ class EVEPlayerCorporation(EVEAPIModel):
logo_color3 = models.IntegerField(blank=True, null=True) logo_color3 = models.IntegerField(blank=True, null=True)
group = models.ForeignKey(Group, blank=True, null=True) group = models.ForeignKey(Group, blank=True, null=True)
applications = models.BooleanField(blank=False, default=False)
objects = EVEPlayerCorporationManager() objects = EVEPlayerCorporationManager()
@@ -192,9 +209,7 @@ class EVEPlayerCorporation(EVEAPIModel):
continue continue
except IndexError: except IndexError:
# Something weird has happened # Something weird has happened
print " * Index Error:", tag_map[0]
continue continue
print "Updating", self.id, self.name
self.api_last_updated = datetime.utcnow() self.api_last_updated = datetime.utcnow()
self.save() self.save()

View File

@@ -27,14 +27,12 @@ class CachedDocumentManager(models.Manager):
# Retrieve the response from the server. # Retrieve the response from the server.
response = conn.getresponse() response = conn.getresponse()
# Save the response (an XML document) to the CachedDocument. # Save the response (an XML document) to the CachedDocument.
cached_doc.body = response.read() cached_doc.body = unicode(response.read(), 'utf-8')
try: try:
# Parse the response via minidom # Parse the response via minidom
dom = minidom.parseString(cached_doc.body) dom = minidom.parseString(cached_doc.body.encode('utf-8'))
except xml.parsers.expat.ExpatError: except xml.parsers.expat.ExpatError:
print "XML Parser Error:"
print cached_doc.body
return return
# Set the CachedDocument's time_retrieved and cached_until times based # Set the CachedDocument's time_retrieved and cached_until times based
@@ -97,7 +95,7 @@ class CachedDocumentManager(models.Manager):
else: else:
# Parse the document here since it was retrieved from the # Parse the document here since it was retrieved from the
# database cache instead of queried for. # database cache instead of queried for.
dom = minidom.parseString(cached_doc.body) dom = minidom.parseString(cached_doc.body.encode('utf-8'))
# Check for the presence errors. Only check the bare minimum, # Check for the presence errors. Only check the bare minimum,
# generic stuff that applies to most or all queries. User-level code # generic stuff that applies to most or all queries. User-level code

0
hr/__init__.py Normal file
View File

17
hr/admin.py Normal file
View File

@@ -0,0 +1,17 @@
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from hr.models import Application, Recommendation
class ApplicationAdmin(admin.ModelAdmin):
list_display = ('user', 'character', 'status')
search_fields = ['user', 'character', 'status']
admin.site.register(Application, ApplicationAdmin)
class RecommendationAdmin(admin.ModelAdmin):
list_display = ('user', 'user_character')
search_fields = ['user_character']
admin.site.register(Recommendation, RecommendationAdmin)

16
hr/app_defines.py Normal file
View File

@@ -0,0 +1,16 @@
APPLICATION_STATUS_NOTSUBMITTED = 0
APPLICATION_STATUS_AWAITINGREVIEW = 1
APPLICATION_STATUS_REJECTED = 2
APPLICATION_STATUS_ACCEPTED = 3
APPLICATION_STATUS_QUERY = 4
APPLICATION_STATUS_COMPLETED = 5
APPLICATION_STATUS_CHOICES = (
(APPLICATION_STATUS_NOTSUBMITTED, 'Not Submitted'),
(APPLICATION_STATUS_AWAITINGREVIEW, 'Awaiting Review'),
(APPLICATION_STATUS_REJECTED, 'Rejected'),
(APPLICATION_STATUS_ACCEPTED, 'Accepted'),
(APPLICATION_STATUS_QUERY, 'In Query'),
(APPLICATION_STATUS_COMPLETED, 'Completed'),
)

40
hr/forms.py Normal file
View File

@@ -0,0 +1,40 @@
from django import forms
import settings
from hr.app_defines import *
from hr.models import Application
from eve_api.models import EVEPlayerCharacter, EVEPlayerCorporation
def CreateRecommendationForm(user):
""" Generate a Recommendation form based on the user's permissions """
characters = EVEPlayerCharacter.objects.filter(eveaccount__user=user)
applications = Application.objects.filter(status=APPLICATION_STATUS_NOTSUBMITTED)
class RecommendationForm(forms.Form):
""" Service Account Form """
character = forms.ModelChoiceField(queryset=characters, required=True, empty_label=None)
application = forms.ModelChoiceField(queryset=applications, required=True, empty_label=None)
return RecommendationForm
def CreateApplicationForm(user):
""" Generate a Application form based on the user's permissions """
characters = EVEPlayerCharacter.objects.filter(eveaccount__user=user)
corporations = EVEPlayerCorporation.objects.filter(applications=True)
class ApplicationForm(forms.Form):
character = forms.ModelChoiceField(queryset=characters, required=True, empty_label=None)
corporation = forms.ModelChoiceField(queryset=corporations, required=True, empty_label=None)
def clean(self):
if len(Application.objects.filter(character=self.cleaned_data['character'], status__in=[APPLICATION_STATUS_NOTSUBMITTED, APPLICATION_STATUS_AWAITINGREVIEW, APPLICATION_STATUS_QUERY])):
raise forms.ValidationError("This character already has a open application")
return self.cleaned_data
return ApplicationForm

40
hr/models.py Normal file
View File

@@ -0,0 +1,40 @@
from datetime import datetime
from django.db import models
from django.contrib.auth.models import User
from eve_api.models import EVEPlayerCharacter, EVEPlayerCorporation
from hr.app_defines import *
class Application(models.Model):
user = models.ForeignKey(User, blank=False, verbose_name="User")
character = models.ForeignKey(EVEPlayerCharacter, blank=False, verbose_name="Character")
corporation = models.ForeignKey(EVEPlayerCorporation, blank=False, verbose_name="Applying to Corporation")
status = models.IntegerField(choices=APPLICATION_STATUS_CHOICES,
default=APPLICATION_STATUS_NOTSUBMITTED,
verbose_name="Status",
help_text="Current status of this application request.")
@property
def status_description(self):
for choice in APPLICATION_STATUS_CHOICES:
if choice[0] == self.status:
return choice[1]
def __unicode__(self):
return self.character.name
def __str__(self):
return self.__unicode__()
class Recommendation(models.Model):
user = models.ForeignKey(User, blank=False, verbose_name="User")
user_character = models.ForeignKey(EVEPlayerCharacter, blank=False, verbose_name="Recommender")
application = models.ForeignKey(Application, blank=False, verbose_name="Recommended Application")
def __unicode__(self):
return self.user_character.name
def __str__(self):
return self.__unicode__()

23
hr/tests.py Normal file
View File

@@ -0,0 +1,23 @@
"""
This file demonstrates two different styles of tests (one doctest and one
unittest). These will both pass when you run "manage.py test".
Replace these with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """
Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}

14
hr/urls.py Normal file
View File

@@ -0,0 +1,14 @@
from django.conf.urls.defaults import *
from hr import views
urlpatterns = patterns('',
('^$', views.index),
(r'^recommendations/$', views.view_recommendations),
(r'^recommendations/(?P<recommendationid>.*)/$', views.view_recommendation),
(r'^applications/$', views.view_applications),
(r'^applications/(?P<applicationid>.*)/$', views.view_application),
(r'^add/application/$', views.add_application),
(r'^add/recommendation/$', views.add_recommendation),
)

118
hr/views.py Normal file
View File

@@ -0,0 +1,118 @@
import datetime
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.template import RequestContext
import settings
from eve_api.models import EVEAccount, EVEPlayerCorporation
from reddit.models import RedditAccount
from hr.forms import CreateRecommendationForm, CreateApplicationForm
from hr.models import Recommendation, Application
def index(request):
if request.user.is_staff or settings.HR_STAFF_GROUP in request.user.groups.all():
hrstaff = True
return render_to_response('hr/index.html', locals(), context_instance=RequestContext(request))
### Application Management
@login_required
def view_applications(request):
apps = Application.objects.filter(user=request.user)
return render_to_response('hr/applications/view_list.html', locals(), context_instance=RequestContext(request))
@login_required
def view_application(request, applicationid):
try:
app = Application.objects.get(id=applicationid)
except Application.DoesNotExist:
return HttpResponseRedirect(reverse('hr.views.index'))
if not app.user == request.user and not (request.user.is_staff or settings.HR_STAFF_GROUP in request.user.groups.all()):
return HttpResponseRedirect(reverse('hr.views.index'))
eveacc = EVEAccount.objects.filter(user=app.user)
redditacc = RedditAccount.objects.filter(user=app.user)
recs = Recommendation.objects.filter(application=app)
posts = []
for acc in redditacc:
posts.extend(acc.recent_posts())
return render_to_response('hr/applications/view.html', locals(), context_instance=RequestContext(request))
@login_required
def add_application(request):
clsform = CreateApplicationForm(request.user)
if request.method == 'POST':
form = clsform(request.POST)
if form.is_valid():
app = Application()
app.user = request.user
app.character = form.cleaned_data['character']
app.corporation = form.cleaned_data['corporation']
app.save()
request.user.message_set.create(message="Your application to %s has been submitted." % app.corporation)
return HttpResponseRedirect(reverse('hr.views.view_applications'))
else:
form = clsform() # An unbound form
if len(EVEPlayerCorporation.objects.filter(applications=True)):
return render_to_response('hr/applications/add.html', locals(), context_instance=RequestContext(request))
else:
return render_to_response('hr/applications/noadd.html', locals(), context_instance=RequestContext(request))
### Recommendation Management
@login_required
def view_recommendations(request):
recs = Recommendation.objects.filter(user=request.user, application__status=0)
return render_to_response('hr/recommendations/view_list.html', locals(), context_instance=RequestContext(request))
@login_required
def view_recommendation(request, recommendationid):
try:
rec = Recommendation.objects.get(id=recommendationid, user=request.user)
except Recommendation.DoesNotExist:
return HttpResponseRedirect(reverse('hr.views.index'))
return render_to_response('hr/recommendations/view.html', locals(), context_instance=RequestContext(request))
@login_required
def add_recommendation(request):
clsform = CreateRecommendationForm(request.user)
if request.method == 'POST':
form = clsform(request.POST)
if form.is_valid():
rec = Recommendation()
rec.user = request.user
rec.user_character = form.cleaned_data['character']
rec.application = form.cleaned_data['application']
rec.created_by = request.user
rec.last_updated_by = request.user
rec.save()
request.user.message_set.create(message="Recommendation added to %s's application" % rec.application )
return HttpResponseRedirect(reverse('hr.views.view_recommendations'))
else:
form = clsform() # An unbound form
return render_to_response('hr/recommendations/add.html', locals(), context_instance=RequestContext(request))

View File

@@ -1,6 +1,5 @@
#!/bin/sh #!/bin/sh
svn checkout http://django-registration.googlecode.com/svn/trunk/registration
svn co http://code.djangoproject.com/svn/django/branches/releases/1.1.X/django django svn co http://code.djangoproject.com/svn/django/branches/releases/1.1.X/django django
svn co http://django-evolution.googlecode.com/svn/trunk/django_evolution svn co http://django-evolution.googlecode.com/svn/trunk/django_evolution
hg clone http://bitbucket.org/jespern/django-piston/ hg clone http://bitbucket.org/jespern/django-piston/

View File

@@ -69,7 +69,7 @@ class MumbleCtlDbus_118(MumbleCtlBase):
info[str(key)] = conf[key]; info[str(key)] = conf[key];
return info; return info;
def getConf(self, srvid, key, value): def getConf(self, srvid, key):
if key == "username": if key == "username":
key = "playername"; key = "playername";
@@ -125,9 +125,10 @@ class MumbleCtlDbus_118(MumbleCtlBase):
ret = {}; ret = {};
for channel in chans: for channel in chans:
print channel;
ret[ channel[0] ] = ObjectInfo( ret[ channel[0] ] = ObjectInfo(
id = int(channel[0]), id = int(channel[0]),
name = str(channel[1]), name = unicode(channel[1]),
parent = int(channel[2]), parent = int(channel[2]),
links = [ int(lnk) for lnk in channel[3] ], links = [ int(lnk) for lnk in channel[3] ],
); );
@@ -149,7 +150,7 @@ class MumbleCtlDbus_118(MumbleCtlBase):
selfDeaf = bool( playerObj[5] ), selfDeaf = bool( playerObj[5] ),
channel = int( playerObj[6] ), channel = int( playerObj[6] ),
userid = int( playerObj[7] ), userid = int( playerObj[7] ),
name = str( playerObj[8] ), name = unicode( playerObj[8] ),
onlinesecs = int( playerObj[9] ), onlinesecs = int( playerObj[9] ),
bytespersec = int( playerObj[10] ) bytespersec = int( playerObj[10] )
); );
@@ -178,7 +179,7 @@ class MumbleCtlDbus_118(MumbleCtlBase):
applySubs = bool(rule[1]), applySubs = bool(rule[1]),
inherited = bool(rule[2]), inherited = bool(rule[2]),
userid = int(rule[3]), userid = int(rule[3]),
group = str(rule[4]), group = unicode(rule[4]),
allow = int(rule[5]), allow = int(rule[5]),
deny = int(rule[6]), deny = int(rule[6]),
) )
@@ -186,7 +187,7 @@ class MumbleCtlDbus_118(MumbleCtlBase):
]; ];
groups = [ ObjectInfo( groups = [ ObjectInfo(
name = str(group[0]), name = unicode(group[0]),
inherited = bool(group[1]), inherited = bool(group[1]),
inherit = bool(group[2]), inherit = bool(group[2]),
inheritable = bool(group[3]), inheritable = bool(group[3]),
@@ -261,7 +262,7 @@ class MumbleCtlDbus_118(MumbleCtlBase):
def setTexture(self, srvid, mumbleid, infile): def setTexture(self, srvid, mumbleid, infile):
# open image, convert to RGBA, and resize to 600x60 # open image, convert to RGBA, and resize to 600x60
img = Image.open( infile ).convert( "RGBA" ).transform( ( 600, 60 ), Image.EXTENT, ( 0, 0, 600, 60 ) ); img = infile.convert( "RGBA" ).transform( ( 600, 60 ), Image.EXTENT, ( 0, 0, 600, 60 ) );
# iterate over the list and pack everything into a string # iterate over the list and pack everything into a string
bgrastring = ""; bgrastring = "";
for ent in list( img.getdata() ): for ent in list( img.getdata() ):

View File

@@ -15,16 +15,20 @@
* GNU General Public License for more details. * GNU General Public License for more details.
""" """
from os.path import exists from time import time
from functools import wraps
from StringIO import StringIO
from os.path import exists, join
from os import unlink, name as os_name
from PIL import Image from PIL import Image
from struct import pack, unpack from struct import pack, unpack
from zlib import compress, decompress from zlib import compress, decompress, error
from mctl import MumbleCtlBase from mctl import MumbleCtlBase
from utils import ObjectInfo from utils import ObjectInfo
import Ice import Ice, IcePy, tempfile
def protectDjangoErrPage( func ): def protectDjangoErrPage( func ):
@@ -36,46 +40,103 @@ def protectDjangoErrPage( func ):
non-existant files and borking. non-existant files and borking.
""" """
def protection_wrapper( *args, **kwargs ): @wraps(func)
def protection_wrapper( self, *args, **kwargs ):
""" Call the original function and catch Ice exceptions. """ """ Call the original function and catch Ice exceptions. """
try: try:
return func( *args, **kwargs ); return func( self, *args, **kwargs );
except Ice.Exception, e: except Ice.Exception, err:
raise e; raise err;
protection_wrapper.innerfunc = func protection_wrapper.innerfunc = func
return protection_wrapper; return protection_wrapper;
@protectDjangoErrPage @protectDjangoErrPage
def MumbleCtlIce( connstring, slicefile ): def MumbleCtlIce( connstring, slicefile=None, icesecret=None ):
""" Choose the correct Ice handler to use (1.1.8 or 1.2.x), and make sure the """ Choose the correct Ice handler to use (1.1.8 or 1.2.x), and make sure the
Murmur version matches the slice Version. Murmur version matches the slice Version.
Optional parameters are the path to the slice file and the Ice secret
necessary to authenticate to Murmur.
The path can be omitted only if running Murmur 1.2.3 or later, which
exports a getSlice method to retrieve the Slice from.
""" """
prop = Ice.createProperties([])
prop.setProperty("Ice.ImplicitContext", "Shared")
idd = Ice.InitializationData()
idd.properties = prop
ice = Ice.initialize(idd)
if icesecret:
ice.getImplicitContext().put( "secret", icesecret.encode("utf-8") )
prx = ice.stringToProxy( connstring.encode("utf-8") )
try:
prx.ice_ping()
except Ice.Exception:
raise EnvironmentError( "Murmur does not appear to be listening on this address (Ice ping failed)." )
try: try:
import Murmur import Murmur
except ImportError: except ImportError:
# Try loading the Slice from Murmur directly via its getSlice method.
# See scripts/testdynamic.py in Mumble's Git repository.
try:
slice = IcePy.Operation( 'getSlice',
Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent,
True, (), (), (), IcePy._t_string, ()
).invoke(prx, ((), None))
except (TypeError, Ice.OperationNotExistException):
if not slicefile: if not slicefile:
raise EnvironmentError( "You didn't configure a slice file. Please set the SLICE variable in settings.py." ) raise EnvironmentError(
"You didn't configure a slice file. Please set the SLICE variable in settings.py." )
if not exists( slicefile ): if not exists( slicefile ):
raise EnvironmentError( "The slice file does not exist: '%s' - please check the settings." % slicefile ) raise EnvironmentError(
"The slice file does not exist: '%s' - please check the settings." % slicefile )
if " " in slicefile: if " " in slicefile:
raise EnvironmentError( "You have a space char in your Slice path. This will confuse Ice, please check." ) raise EnvironmentError(
"You have a space char in your Slice path. This will confuse Ice, please check." )
if not slicefile.endswith( ".ice" ): if not slicefile.endswith( ".ice" ):
raise EnvironmentError( "The slice file name MUST end with '.ice'." ) raise EnvironmentError( "The slice file name MUST end with '.ice'." )
try:
Ice.loadSlice( slicefile ) Ice.loadSlice( slicefile )
except RuntimeError:
raise RuntimeError( "Slice preprocessing failed. Please check your server's error log." )
else:
if os_name == "nt":
# It weren't Windows if it didn't need to be treated differently. *sigh*
temppath = join( tempfile.gettempdir(), "Murmur.ice" )
slicetemp = open( temppath, "w+b" )
try:
slicetemp.write( slice )
finally:
slicetemp.close()
try:
Ice.loadSlice( temppath )
except RuntimeError:
raise RuntimeError( "Slice preprocessing failed. Please check your server's error log." )
finally:
unlink(temppath)
else:
slicetemp = tempfile.NamedTemporaryFile( suffix='.ice' )
try:
slicetemp.write( slice )
slicetemp.flush()
Ice.loadSlice( slicetemp.name )
except RuntimeError:
raise RuntimeError( "Slice preprocessing failed. Please check your server's error log." )
finally:
slicetemp.close()
import Murmur import Murmur
ice = Ice.initialize()
prx = ice.stringToProxy( connstring.encode("utf-8") )
meta = Murmur.MetaPrx.checkedCast(prx) meta = Murmur.MetaPrx.checkedCast(prx)
murmurversion = meta.getVersion()[:3] murmurversion = meta.getVersion()[:3]
@@ -84,8 +145,17 @@ def MumbleCtlIce( connstring, slicefile ):
return MumbleCtlIce_118( connstring, meta ); return MumbleCtlIce_118( connstring, meta );
elif murmurversion[:2] == (1, 2): elif murmurversion[:2] == (1, 2):
if murmurversion[2] < 2:
return MumbleCtlIce_120( connstring, meta ); return MumbleCtlIce_120( connstring, meta );
elif murmurversion[2] == 2:
return MumbleCtlIce_122( connstring, meta );
elif murmurversion[2] == 3:
return MumbleCtlIce_123( connstring, meta );
raise NotImplementedError( "No ctl object available for Murmur version %d.%d.%d" % tuple(murmurversion) )
class MumbleCtlIce_118(MumbleCtlBase): class MumbleCtlIce_118(MumbleCtlBase):
method = "ICE"; method = "ICE";
@@ -291,7 +361,10 @@ class MumbleCtlIce_118(MumbleCtlBase):
if len(texture) == 0: if len(texture) == 0:
raise ValueError( "No Texture has been set." ); raise ValueError( "No Texture has been set." );
# this returns a list of bytes. # this returns a list of bytes.
try:
decompressed = decompress( texture ); decompressed = decompress( texture );
except error, err:
raise ValueError( err )
# iterate over 4 byte chunks of the string # iterate over 4 byte chunks of the string
imgdata = ""; imgdata = "";
for idx in range( 0, len(decompressed), 4 ): for idx in range( 0, len(decompressed), 4 ):
@@ -308,7 +381,7 @@ class MumbleCtlIce_118(MumbleCtlBase):
@protectDjangoErrPage @protectDjangoErrPage
def setTexture(self, srvid, mumbleid, infile): def setTexture(self, srvid, mumbleid, infile):
# open image, convert to RGBA, and resize to 600x60 # open image, convert to RGBA, and resize to 600x60
img = Image.open( infile ).convert( "RGBA" ).transform( ( 600, 60 ), Image.EXTENT, ( 0, 0, 600, 60 ) ); img = infile.convert( "RGBA" ).transform( ( 600, 60 ), Image.EXTENT, ( 0, 0, 600, 60 ) );
# iterate over the list and pack everything into a string # iterate over the list and pack everything into a string
bgrastring = ""; bgrastring = "";
for ent in list( img.getdata() ): for ent in list( img.getdata() ):
@@ -360,7 +433,20 @@ class MumbleCtlIce_120(MumbleCtlIce_118):
@protectDjangoErrPage @protectDjangoErrPage
def getPlayers(self, srvid): def getPlayers(self, srvid):
return self._getIceServerObject(srvid).getUsers(); userdata = self._getIceServerObject(srvid).getUsers();
for key in userdata:
if isinstance( userdata[key], str ):
userdata[key] = userdata[key].decode( "UTF-8" )
return userdata
@protectDjangoErrPage
def getState(self, srvid, sessionid):
userdata = self._getIceServerObject(srvid).getState(sessionid);
for key in userdata.__dict__:
attr = getattr( userdata, key )
if isinstance( attr, str ):
setattr( userdata, key, attr.decode( "UTF-8" ) )
return userdata
@protectDjangoErrPage @protectDjangoErrPage
def registerPlayer(self, srvid, name, email, password): def registerPlayer(self, srvid, name, email, password):
@@ -434,3 +520,76 @@ class MumbleCtlIce_120(MumbleCtlIce_118):
def setACL(self, srvid, channelid, acls, groups, inherit): def setACL(self, srvid, channelid, acls, groups, inherit):
return self._getIceServerObject(srvid).setACL( channelid, acls, groups, inherit ); return self._getIceServerObject(srvid).setACL( channelid, acls, groups, inherit );
@protectDjangoErrPage
def getBans(self, srvid):
return self._getIceServerObject(srvid).getBans();
@protectDjangoErrPage
def setBans(self, srvid, bans):
return self._getIceServerObject(srvid).setBans(bans);
@protectDjangoErrPage
def addBanForSession(self, srvid, sessionid, **kwargs):
session = self.getState(srvid, sessionid);
if "bits" not in kwargs:
kwargs["bits"] = 128;
if "start" not in kwargs:
kwargs["start"] = int(time());
if "duration" not in kwargs:
kwargs["duration"] = 3600;
return self.addBan(srvid, address=session.address, **kwargs);
@protectDjangoErrPage
def addBan(self, srvid, **kwargs):
for key in kwargs:
if isinstance( kwargs[key], unicode ):
kwargs[key] = kwargs[key].encode("UTF-8")
from Murmur import Ban
srvbans = self.getBans(srvid);
srvbans.append( Ban( **kwargs ) );
return self.setBans(srvid, srvbans);
@protectDjangoErrPage
def kickUser(self, srvid, userid, reason=""):
return self._getIceServerObject(srvid).kickUser( userid, reason.encode("UTF-8") );
class MumbleCtlIce_122(MumbleCtlIce_120):
@protectDjangoErrPage
def getTexture(self, srvid, mumbleid):
raise ValueError( "This method is buggy in 1.2.2, sorry dude." );
@protectDjangoErrPage
def setTexture(self, srvid, mumbleid, infile):
buf = StringIO()
infile.save( buf, "PNG" )
buf.seek(0)
self._getIceServerObject(srvid).setTexture(mumbleid, buf.read())
class MumbleCtlIce_123(MumbleCtlIce_120):
@protectDjangoErrPage
def getRawTexture(self, srvid, mumbleid):
return self._getIceServerObject(srvid).getTexture(mumbleid)
@protectDjangoErrPage
def getTexture(self, srvid, mumbleid):
texture = self.getRawTexture(srvid, mumbleid)
if len(texture) == 0:
raise ValueError( "No Texture has been set." );
from StringIO import StringIO
try:
return Image.open( StringIO( texture ) )
except IOError, err:
raise ValueError( err )
@protectDjangoErrPage
def setTexture(self, srvid, mumbleid, infile):
buf = StringIO()
infile.save( buf, "PNG" )
buf.seek(0)
self._getIceServerObject(srvid).setTexture(mumbleid, buf.read())

View File

@@ -14,27 +14,61 @@
* GNU General Public License for more details. * GNU General Public License for more details.
""" """
from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
#from mumble.forms import MumbleAdminForm, MumbleUserAdminForm from mumble.forms import MumbleServerForm, MumbleAdminForm, MumbleUserAdminForm
from mumble.models import Mumble, MumbleUser from mumble.models import MumbleServer, Mumble, MumbleUser
class MumbleServerAdmin(admin.ModelAdmin):
list_display = [ 'dbus', 'get_murmur_online' ]
search_fields = [ 'dbus' ]
ordering = [ 'dbus' ]
form = MumbleServerForm
def get_murmur_online( self, obj ):
return obj.online
get_murmur_online.short_description = _('Master is running')
get_murmur_online.boolean = True
class MumbleAdmin(admin.ModelAdmin): class MumbleAdmin(admin.ModelAdmin):
""" Specification for the "Server administration" admin section. """ """ Specification for the "Server administration" admin section. """
list_display = [ 'name', 'addr', 'port', 'get_booted', 'get_is_public', list_display = [ 'name', 'srvid', 'get_addr', 'get_port', 'get_murmur_online', 'get_booted',
'get_users_regged', 'get_users_online', 'get_channel_count' ]; 'get_is_public', 'get_users_regged', 'get_users_online', 'get_channel_count' ];
list_filter = [ 'addr' ]; list_filter = [ 'addr', 'server' ];
search_fields = [ 'name', 'addr', 'port' ]; search_fields = [ 'name', 'addr', 'port' ];
ordering = [ 'name' ]; ordering = [ 'name' ];
#form = MumbleAdminForm; form = MumbleAdminForm;
def get_murmur_online( self, obj ):
return obj.server.online
get_murmur_online.short_description = _('Master is running')
get_murmur_online.boolean = True
def get_addr( self, obj ):
if not obj.addr:
return "*"
return obj.addr
get_addr.short_description = _('Server Address')
def get_port( self, obj ):
if not obj.port:
return "< %d >" % (settings.MUMBLE_DEFAULT_PORT + obj.srvid - 1)
return obj.port
get_port.short_description = _('Server Port')
def get_booted( self, obj ): def get_booted( self, obj ):
return obj.booted return obj.booted
get_booted.short_description = _('Boot Server') get_booted.short_description = _('Instance is running')
get_booted.boolean = True get_booted.boolean = True
def get_users_regged( self, obj ): def get_users_regged( self, obj ):
@@ -88,14 +122,17 @@ class MumbleUserAdmin(admin.ModelAdmin):
search_fields = [ 'owner__username', 'name' ]; search_fields = [ 'owner__username', 'name' ];
ordering = [ 'owner__username' ]; ordering = [ 'owner__username' ];
#form = MumbleUserAdminForm form = MumbleUserAdminForm
def get_acl_admin( self, obj ): def get_acl_admin( self, obj ):
if obj.server.booted:
return obj.aclAdmin return obj.aclAdmin
return None
get_acl_admin.short_description = _('Admin on root channel') get_acl_admin.short_description = _('Admin on root channel')
get_acl_admin.boolean = True get_acl_admin.boolean = True
admin.site.register( MumbleServer, MumbleServerAdmin );
admin.site.register( Mumble, MumbleAdmin ); admin.site.register( Mumble, MumbleAdmin );
admin.site.register( MumbleUser, MumbleUserAdmin ); admin.site.register( MumbleUser, MumbleUserAdmin );

View File

@@ -0,0 +1,18 @@
BEGIN;
-- Model: Mumble
ALTER TABLE `mumble_mumble`
ADD `server_id` integer FIRST;
ALTER TABLE `mumble_mumble`
ADD `display` varchar(200) AFTER `name`;
ALTER TABLE `mumble_mumble`
MODIFY `port` integer NULL;
COMMIT;
BEGIN;
ALTER TABLE mumble_mumble DROP KEY `addr`;
COMMIT;
BEGIN;
CREATE INDEX `mumble_mumble_server_id_idx`
ON `mumble_mumble` (`server_id`);
COMMIT;

View File

@@ -0,0 +1,3 @@
INSERT INTO `mumble_mumbleserver` ( `dbus`, `secret` )
SELECT DISTINCT `dbus`, ''
FROM `mumble_mumble`;

View File

@@ -0,0 +1,6 @@
UPDATE `mumble_mumble`
SET `server_id`=(
SELECT `id`
FROM `mumble_mumbleserver`
WHERE `mumble_mumbleserver`.`dbus` = `mumble_mumble`.`dbus`
);

View File

@@ -0,0 +1,29 @@
-- Model: Mumble
ALTER TABLE `mumble_mumble`
DROP COLUMN `dbus`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `url`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `motd`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `passwd`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `supw`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `users`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `bwidth`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `sslcrt`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `sslkey`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `obfsc`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `player`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `channel`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `defchan`;
ALTER TABLE `mumble_mumble`
DROP COLUMN `booted`;

View File

@@ -0,0 +1,3 @@
-- Model: MumbleUser
ALTER TABLE `mumble_mumbleuser`
DROP COLUMN `isAdmin`;

View File

@@ -0,0 +1,13 @@
-- Model: Mumble
BEGIN;
ALTER TABLE "mumble_mumble"
ADD "server_id" integer NULL REFERENCES "mumble_mumbleserver" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "mumble_mumble"
ADD "display" varchar(200);
ALTER TABLE "mumble_mumble"
ALTER "port" DROP NOT NULL;
CREATE INDEX "mumble_mumble_server_id" ON "mumble_mumble" ("server_id");
ALTER TABLE "mumble_mumble" DROP CONSTRAINT "mumble_mumble_addr_key";
COMMIT;

View File

@@ -0,0 +1,5 @@
BEGIN;
INSERT INTO "mumble_mumbleserver" ( "dbus", "secret" )
SELECT DISTINCT "dbus", ''
FROM "mumble_mumble";
COMMIT;

View File

@@ -0,0 +1,8 @@
BEGIN;
UPDATE "mumble_mumble"
SET "server_id"=(
SELECT "id"
FROM "mumble_mumbleserver"
WHERE "mumble_mumbleserver"."dbus" = "mumble_mumble"."dbus"
);
COMMIT;

View File

@@ -0,0 +1,34 @@
-- Model: Mumble
BEGIN;
ALTER TABLE "mumble_mumble"
DROP COLUMN "dbus";
ALTER TABLE "mumble_mumble"
DROP COLUMN "url";
ALTER TABLE "mumble_mumble"
DROP COLUMN "motd";
ALTER TABLE "mumble_mumble"
DROP COLUMN "passwd";
ALTER TABLE "mumble_mumble"
DROP COLUMN "supw";
ALTER TABLE "mumble_mumble"
DROP COLUMN "users";
ALTER TABLE "mumble_mumble"
DROP COLUMN "bwidth";
ALTER TABLE "mumble_mumble"
DROP COLUMN "sslcrt";
ALTER TABLE "mumble_mumble"
DROP COLUMN "sslkey";
ALTER TABLE "mumble_mumble"
DROP COLUMN "obfsc";
ALTER TABLE "mumble_mumble"
DROP COLUMN "player";
ALTER TABLE "mumble_mumble"
DROP COLUMN "channel";
ALTER TABLE "mumble_mumble"
DROP COLUMN "defchan";
ALTER TABLE "mumble_mumble"
DROP COLUMN "booted";
ALTER TABLE "mumble_mumble"
ALTER COLUMN "server_id" SET NOT NULL;
COMMIT;

View File

@@ -0,0 +1,5 @@
-- Model: MumbleUser
BEGIN;
ALTER TABLE "mumble_mumbleuser"
DROP COLUMN "isAdmin";
COMMIT;

View File

@@ -0,0 +1,10 @@
CREATE TABLE "mumble_mumble_new" (
"id" integer NOT NULL PRIMARY KEY,
"server_id" integer NOT NULL REFERENCES "mumble_mumbleserver" ("id"),
"name" varchar(200) NOT NULL,
"srvid" integer NOT NULL,
"addr" varchar(200) NOT NULL,
"port" integer,
"display" varchar(200) NOT NULL,
UNIQUE ("server_id", "srvid")
);

View File

@@ -0,0 +1,10 @@
CREATE TABLE "mumble_mumbleuser_new" (
"id" integer NOT NULL PRIMARY KEY,
"mumbleid" integer NOT NULL,
"name" varchar(200) NOT NULL,
"password" varchar(200) NOT NULL,
"server_id" integer NOT NULL REFERENCES "mumble_mumble" ("id"),
"owner_id" integer REFERENCES "auth_user" ("id"),
UNIQUE ("server_id", "owner_id"),
UNIQUE ("server_id", "mumbleid")
);

View File

@@ -0,0 +1,3 @@
INSERT INTO "mumble_mumbleserver" ( "dbus", "secret" )
SELECT DISTINCT "dbus", ''
FROM "mumble_mumble";

View File

@@ -0,0 +1,11 @@
INSERT INTO "mumble_mumble_new"
SELECT
"mumble_mumble"."id",
"mumble_mumbleserver"."id",
"mumble_mumble"."name",
"mumble_mumble"."srvid",
"mumble_mumble"."addr",
"mumble_mumble"."port",
''
FROM "mumble_mumble" INNER JOIN "mumble_mumbleserver"
WHERE "mumble_mumbleserver"."dbus" = "mumble_mumble"."dbus";

View File

@@ -0,0 +1,9 @@
INSERT INTO "mumble_mumbleuser_new"
SELECT
"id",
"mumbleid",
"name",
"password",
"server_id",
"owner_id"
FROM "mumble_mumbleuser";

View File

@@ -0,0 +1 @@
DROP TABLE "mumble_mumble";

View File

@@ -0,0 +1 @@
ALTER TABLE "mumble_mumble_new" RENAME TO "mumble_mumble";

View File

@@ -0,0 +1 @@
DROP TABLE "mumble_mumbleuser";

View File

@@ -0,0 +1 @@
ALTER TABLE "mumble_mumbleuser_new" RENAME TO "mumble_mumbleuser";

View File

@@ -0,0 +1 @@
SEQUENCE = ['mumble-db-update-1']

View File

@@ -0,0 +1,10 @@
from django_evolution.mutations import *
from django.db import models
MUTATIONS = [
AddField('Mumble', 'display', models.CharField, initial='', max_length=200),
AddField('Mumble', 'server', models.ForeignKey, initial='<<USER VALUE REQUIRED>>', related_model='mumble.MumbleServer'),
DeleteField('Mumble', 'dbus'),
ChangeField('Mumble', 'port', initial=None, null=True)
]

324
mumble/forms.py Normal file
View File

@@ -0,0 +1,324 @@
# -*- coding: utf-8 -*-
"""
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
*
* Mumble-Django is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
"""
import socket
import re
from django import forms
from django.conf import settings
from django.forms import Form, ModelForm
from django.utils.translation import ugettext_lazy as _
from mumble.models import MumbleServer, Mumble, MumbleUser
class PropertyModelForm( ModelForm ):
""" ModelForm that gets/sets fields that are not within the model's
fields as model attributes. Necessary to get forms that manipulate
properties.
"""
def __init__( self, *args, **kwargs ):
ModelForm.__init__( self, *args, **kwargs );
if self.instance:
instfields = self.instance._meta.get_all_field_names()
for fldname in self.fields:
if fldname in instfields:
continue
self.fields[fldname].initial = getattr( self.instance, fldname )
prop = getattr( self.instance.__class__, fldname )
if prop.__doc__:
self.fields[fldname].label = _(prop.__doc__)
def save( self, commit=True ):
inst = ModelForm.save( self, commit=commit )
if commit:
self.save_to_model( inst )
else:
# Update when the model has been saved.
from django.db.models import signals
self._update_inst = inst
signals.post_save.connect( self.save_listener, sender=inst.__class__ )
return inst
def save_listener( self, **kwargs ):
if kwargs['instance'] is self._update_inst:
self.save_to_model( self._update_inst )
def save_to_model( self, inst ):
instfields = inst._meta.get_all_field_names()
for fldname in self.fields:
if fldname not in instfields:
setattr( inst, fldname, self.cleaned_data[fldname] )
class MumbleForm( PropertyModelForm ):
""" The Mumble Server admin form that allows to configure settings which do
not necessarily have to be reserved to the server hoster.
Server hosters are expected to use the Django admin application instead,
where everything can be configured freely.
"""
url = forms.CharField( required=False )
motd = forms.CharField( required=False, widget=forms.Textarea )
passwd = forms.CharField( required=False, help_text=_(
"Password required to join. Leave empty for public servers.") )
supw = forms.CharField( required=False )
obfsc = forms.BooleanField( required=False, help_text=_(
"If on, IP adresses of the clients are not logged.") )
player = forms.CharField( required=False )
channel = forms.CharField( required=False )
defchan = forms.TypedChoiceField( choices=(), coerce=int, required=False )
timeout = forms.IntegerField( required=False )
certreq = forms.BooleanField( required=False )
textlen = forms.IntegerField( required=False )
html = forms.BooleanField( required=False )
def __init__( self, *args, **kwargs ):
PropertyModelForm.__init__( self, *args, **kwargs )
# Populate the `default channel' field's choices
choices = [ ('', '----------') ]
if self.instance and self.instance.srvid is not None:
if self.instance.booted:
def add_item( item, level ):
if item.is_server or item.is_channel:
choices.append( ( item.chanid, ( "-"*level + " " + item.name ) ) )
self.instance.rootchan.visit(add_item)
else:
current = self.instance.defchan
if current is not None:
choices.append( ( current, "Current value: %d" % current ) )
self.fields['defchan'].choices = choices
class Meta:
model = Mumble;
fields = ['name'];
class MumbleAdminForm( MumbleForm ):
""" A Mumble Server admin form intended to be used by the server hoster. """
users = forms.IntegerField( required=False )
bwidth = forms.IntegerField( required=False )
sslcrt = forms.CharField( required=False, widget=forms.Textarea )
sslkey = forms.CharField( required=False, widget=forms.Textarea )
booted = forms.BooleanField( required=False )
autoboot = forms.BooleanField( required=False )
bonjour = forms.BooleanField( required=False )
class Meta:
fields = None
exclude = None
def clean_port( self ):
""" Check if the port number is valid. """
port = self.cleaned_data['port'];
if port is not None and port != '':
if port < 1 or port >= 2**16:
raise forms.ValidationError(
_("Port number %(portno)d is not within the allowed range %(minrange)d - %(maxrange)d") % {
'portno': port,
'minrange': 1,
'maxrange': 2**16,
});
return port;
return None
class MumbleServerForm( ModelForm ):
defaultconf = forms.CharField( label=_("Default config"), required=False, widget=forms.Textarea )
def __init__( self, *args, **kwargs ):
ModelForm.__init__( self, *args, **kwargs )
if self.instance and self.instance.id:
if self.instance.online:
confstr = ""
conf = self.instance.defaultconf
for field in conf:
confstr += "%s: %s\n" % ( field, conf[field] )
self.fields["defaultconf"].initial = confstr
else:
self.fields["defaultconf"].initial = _("This server is currently offline.")
class Meta:
model = MumbleServer
class MumbleUserForm( ModelForm ):
""" The user registration form used to register an account. """
password = forms.CharField( widget=forms.PasswordInput, required=False )
def __init__( self, *args, **kwargs ):
ModelForm.__init__( self, *args, **kwargs );
self.server = None;
def clean_name( self ):
""" Check if the desired name is forbidden or taken. """
name = self.cleaned_data['name'];
if self.server is None:
raise AttributeError( "You need to set the form's server attribute to the server instance "
"for validation to work." );
if self.server.player and re.compile( self.server.player ).match( name ) is None:
raise forms.ValidationError( _( "That name is forbidden by the server." ) );
if not self.instance.id and len( self.server.ctl.getRegisteredPlayers( self.server.srvid, name ) ) > 0:
raise forms.ValidationError( _( "Another player already registered that name." ) );
return name;
def clean_password( self ):
""" Verify a password has been given. """
passwd = self.cleaned_data['password'];
if not passwd and ( not self.instance or self.instance.mumbleid == -1 ):
raise forms.ValidationError( _( "Cannot register player without a password!" ) );
return passwd;
class Meta:
model = MumbleUser;
fields = ( 'name', 'password' );
class MumbleUserPasswordForm( MumbleUserForm ):
""" The user registration form used to register an account on a private server in protected mode. """
serverpw = forms.CharField(
label=_('Server Password'),
help_text=_('This server is private and protected mode is active. Please enter the server password.'),
widget=forms.PasswordInput(render_value=False)
);
def clean_serverpw( self ):
""" Validate the password """
serverpw = self.cleaned_data['serverpw'];
if self.server.passwd != serverpw:
raise forms.ValidationError( _( "The password you entered is incorrect." ) );
return serverpw;
def clean( self ):
""" prevent save() from trying to store the password in the Model instance. """
# clean() will be called after clean_serverpw(), so it has already been validated here.
if 'serverpw' in self.cleaned_data:
del( self.cleaned_data['serverpw'] );
return self.cleaned_data;
class MumbleUserLinkForm( MumbleUserForm ):
""" Special registration form to either register or link an account. """
linkacc = forms.BooleanField(
label=_('Link account'),
help_text=_('The account already exists and belongs to me, just link it instead of creating.'),
required=False,
);
def __init__( self, *args, **kwargs ):
MumbleUserForm.__init__( self, *args, **kwargs );
self.mumbleid = None;
def clean_name( self ):
""" Check if the target account exists in Murmur. """
if 'linkacc' not in self.data:
return MumbleUserForm.clean_name( self );
# Check if user exists
name = self.cleaned_data['name'];
if len( self.server.ctl.getRegisteredPlayers( self.server.srvid, name ) ) != 1:
raise forms.ValidationError( _( "No such user found." ) );
return name;
def clean_password( self ):
""" Verify that the password is correct. """
if 'linkacc' not in self.data:
return MumbleUserForm.clean_password( self );
if 'name' not in self.cleaned_data:
# keep clean() from trying to find a user that CAN'T exist
self.mumbleid = -10;
return '';
# Validate password with Murmur
passwd = self.cleaned_data['password'];
self.mumbleid = self.server.ctl.verifyPassword( self.server.srvid, self.cleaned_data['name'], passwd )
if self.mumbleid <= 0:
raise forms.ValidationError( _( "The password you entered is incorrect." ) );
return passwd;
def clean( self ):
""" Create the MumbleUser instance to save in. """
if 'linkacc' not in self.data or self.mumbleid <= 0:
return self.cleaned_data;
try:
m_user = MumbleUser.objects.get( server=self.server, mumbleid=self.mumbleid );
except MumbleUser.DoesNotExist:
m_user = MumbleUser( server=self.server, name=self.cleaned_data['name'], mumbleid=self.mumbleid );
m_user.save( dontConfigureMurmur=True );
else:
if m_user.owner is not None:
raise forms.ValidationError( _( "That account belongs to someone else." ) );
if m_user.getAdmin() and not settings.ALLOW_ACCOUNT_LINKING_ADMINS:
raise forms.ValidationError( _( "Linking Admin accounts is not allowed." ) );
self.instance = m_user;
return self.cleaned_data;
class MumbleUserAdminForm( PropertyModelForm ):
aclAdmin = forms.BooleanField( required=False );
password = forms.CharField( widget=forms.PasswordInput, required=False )
def clean_password( self ):
""" Verify a password has been given. """
passwd = self.cleaned_data['password'];
if not passwd and ( not self.instance or self.instance.mumbleid == -1 ):
raise forms.ValidationError( _( "Cannot register player without a password!" ) );
return passwd;
class Meta:
model = Mumble;
class MumbleKickForm( Form ):
session = forms.IntegerField();
ban = forms.BooleanField( required=False );
reason = forms.CharField( required=False );
class MumbleTextureForm( Form ):
""" The form used to upload a new image to be set as texture. """
texturefile = forms.ImageField( label=_("User Texture") );

Binary file not shown.

View File

@@ -0,0 +1,580 @@
# Croatian translation file for Mumble-Django.
#
# Copyright (C) 2009, Vid "Night" Marić <vid_maric@yahoo.com>
#
# Mumble-Django is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
msgid ""
msgstr ""
"Project-Id-Version: Mumble-Django v0.8\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-03-11 16:43+0100\n"
"PO-Revision-Date: 2010-01-11 14:11\n"
"Last-Translator: <de@de.de>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Translated-Using: django-rosetta 0.5.1\n"
#: admin.py:34 admin.py:51
msgid "Master is running"
msgstr ""
#: admin.py:59 models.py:162 templates/mumble/mumble.html:28
msgid "Server Address"
msgstr "Adresa servera"
#: admin.py:66 models.py:165
msgid "Server Port"
msgstr "Port"
#: admin.py:71
msgid "Instance is running"
msgstr ""
#: admin.py:81
msgid "Registered users"
msgstr "Registrirani korisnici"
#: admin.py:91
msgid "Online users"
msgstr "Korisnici na serveru"
#: admin.py:101
msgid "Channel count"
msgstr "Broj kanala"
#: admin.py:108
msgid "Yes"
msgstr ""
#: admin.py:110
msgid "No"
msgstr ""
#: admin.py:114
msgid "Public"
msgstr "Javni server"
#: admin.py:132 models.py:626 templates/mumble/mumble.html:223
msgid "Admin on root channel"
msgstr "Administrator u glavnoj sobi"
#: forms.py:83
msgid "Password required to join. Leave empty for public servers."
msgstr ""
"Lozinka za pristup serveru. Ostaviti prazno ako se spajate na javni server."
#: forms.py:86
msgid "If on, IP adresses of the clients are not logged."
msgstr "Ako je uključeno, IP adrese klijenata se neće bilježiti."
#: forms.py:142
#, python-format
msgid ""
"Port number %(portno)d is not within the allowed range %(minrange)d - %"
"(maxrange)d"
msgstr "Port %(portno)d nije u dozvoljenom opsegu %(minrange)d - %(maxrange)d"
#: forms.py:165 templates/mumble/offline.html:12
msgid "This server is currently offline."
msgstr ""
#: forms.py:190
msgid "That name is forbidden by the server."
msgstr "Ime je zabranjeno na serveru."
#: forms.py:193 models.py:582
msgid "Another player already registered that name."
msgstr "Ovo ime je zauzeto, probajte neko drugo."
#: forms.py:201 forms.py:307 models.py:584
msgid "Cannot register player without a password!"
msgstr "Odaberite lozinku i probajte ponovno!"
#: forms.py:213 models.py:179
msgid "Server Password"
msgstr "Lozinka servera"
#: forms.py:214
msgid ""
"This server is private and protected mode is active. Please enter the server "
"password."
msgstr ""
"Ovo je privatan server i potrebna vam je lozinka za pristup. Molimo vas "
"unesite lozinku."
#: forms.py:222 forms.py:274
msgid "The password you entered is incorrect."
msgstr "Lozinka koju ste unijeli je neispravna."
#: forms.py:237
msgid "Link account"
msgstr "Povezani račun"
#: forms.py:238
msgid ""
"The account already exists and belongs to me, just link it instead of "
"creating."
msgstr "Ovaj račun već postoji. Povežite ga umjesto da radite novi račun."
#: forms.py:255
msgid "No such user found."
msgstr "Korisnik nije pronađen."
#: forms.py:290
msgid "That account belongs to someone else."
msgstr "Ovaj račun pripada drugome korisniku."
#: forms.py:293
msgid "Linking Admin accounts is not allowed."
msgstr "Povezivanje administratorskih računa nije dozvoljeno."
#: forms.py:322 templates/mumble/mumble.html:65
#: templates/mumble/mumble.html.py:148 templates/mumble/mumble.html:278
msgid "User Texture"
msgstr "Korisnička slika"
#: models.py:63
msgid "DBus or ICE base"
msgstr "DBus ili ICE"
#: models.py:64
msgid ""
"Examples: 'net.sourceforge.mumble.murmur' for DBus or 'Meta:tcp -h 127.0.0.1 "
"-p 6502' for Ice."
msgstr ""
"Primjeri: 'net.sourceforge.mumble.murmur' za DBus ili 'Meta:tcp -h 127.0.0.1 "
"-p 6502' za Ice."
#: models.py:65
msgid "Ice Secret"
msgstr ""
#: models.py:68
#, fuzzy
msgid "Mumble Server"
msgstr "ID korisnika na Mumbleu"
#: models.py:69
#, fuzzy
msgid "Mumble Servers"
msgstr "ID korisnika na Mumbleu"
#: models.py:160
msgid "Server Name"
msgstr "Ime servera"
#: models.py:161
msgid "Server ID"
msgstr "ID servera"
#: models.py:163
msgid ""
"Hostname or IP address to bind to. You should use a hostname here, because "
"it will appear on the global server list."
msgstr ""
"Ime poslužitelja (hostname) ili IP adresa adresa za spajanje. Koristite ime "
"poslužitelja ovdje zato što će se pojaviti na globalnoj listi servera."
#: models.py:166
#, fuzzy
msgid "Port number to bind to. Leave empty to auto assign one."
msgstr "Port za spajanje. Koristite -1 za automatsko dodjeljivanje porta."
#: models.py:167
#, fuzzy
msgid "Server Display Address"
msgstr "Adresa servera"
#: models.py:168
msgid ""
"This field is only relevant if you are located behind a NAT, and names the "
"Hostname or IP address to use in the Channel Viewer and for the global "
"server list registration. If not given, the addr and port fields are used. "
"If display and bind ports are equal, you can omit it here."
msgstr ""
#: models.py:174
msgid "Superuser Password"
msgstr "Lozinka Superusera (administrator)"
#: models.py:177
msgid "Website URL"
msgstr "URL internet stranice"
#: models.py:178
msgid "Welcome Message"
msgstr "Poruka dobrodošlice"
#: models.py:180
msgid "Max. Users"
msgstr "Maksimalan broj korisnika"
#: models.py:181
msgid "Bandwidth [Bps]"
msgstr "Promet [Bps]"
#: models.py:182
msgid "SSL Certificate"
msgstr "SSL certifikat"
#: models.py:183
msgid "SSL Key"
msgstr "SSL ključ"
#: models.py:184
msgid "Player name regex"
msgstr "Dozvoljeni znakovi u nazivu korisnika"
#: models.py:185
msgid "Channel name regex"
msgstr "Dozvoljeni znakovi u nazivu kanala"
#: models.py:186
msgid "Default channel"
msgstr "Početni kanal"
#: models.py:187
msgid "Timeout"
msgstr ""
#: models.py:189
msgid "IP Obfuscation"
msgstr "Bilježi IP adrese korisnika"
#: models.py:190
#, fuzzy
msgid "Require Certificate"
msgstr "SSL certifikat"
#: models.py:191
msgid "Maximum length of text messages"
msgstr ""
#: models.py:192
msgid "Allow HTML to be used in messages"
msgstr ""
#: models.py:193
msgid "Publish this server via Bonjour"
msgstr ""
#: models.py:194
msgid "Boot Server when Murmur starts"
msgstr ""
#: models.py:212 models.py:213
msgid "Boot Server"
msgstr "Pokreni server"
#: models.py:217 models.py:545
msgid "Server instance"
msgstr "Instanca servera"
#: models.py:218
msgid "Server instances"
msgstr "Instance servera"
#: models.py:505 models.py:685
msgid "This field must not be updated once the record has been saved."
msgstr "Ovo polje ne smije biti ažurirano nakon što je zapis spremljen."
#: models.py:542
msgid "Mumble player_id"
msgstr "ID korisnika na Mumbleu"
#: models.py:543
msgid "User name and Login"
msgstr "Korisničko ime"
#: models.py:544
msgid "Login password"
msgstr "Lozinka"
#: models.py:546 templates/mumble/mumble.html:293
msgid "Account owner"
msgstr "Vlasnik računa"
#: models.py:548
#, fuzzy
msgid "The user's comment."
msgstr "Korisnički račun"
#: models.py:549
msgid "The user's hash."
msgstr ""
#: models.py:553
msgid "User account"
msgstr "Korisnički račun"
#: models.py:554
msgid "User accounts"
msgstr "Korisnički računi"
#: models.py:561
#, python-format
msgid "Mumble user %(mu)s on %(srv)s owned by Django user %(du)s"
msgstr "Django korisniku %(du)s pripada Mumble račun %(mu)s na serveru %(srv)s"
#: templates/mumble/list.html:20
msgid "No server instances have been configured yet."
msgstr ""
#: templates/mumble/mumble.html:16
msgid ""
"\n"
" <b>Hint:</b><br />\n"
" This area is used to display additional information for each channel "
"and player, but requires JavaScript to be\n"
" displayed correctly. You will not see the detail pages, but you can "
"use all links and forms\n"
" that are displayed.\n"
" "
msgstr ""
"\n"
" <b>Savjet:</b><br />\n"
" Ovdje se prikazuju dodatne informacije za svaki kanali svakog igrača "
"te zahtjeva JavaScript kako bi\n"
" se informacije pravilno prikazale. Nećete vidjeti stranicu s "
"detaljima, ali možete koristiti sve linkove i forme.\n"
" "
#: templates/mumble/mumble.html:31
msgid "Website"
msgstr "Internet stranica"
#: templates/mumble/mumble.html:33
msgid "Server version"
msgstr "Verzija servera"
#: templates/mumble/mumble.html:34
msgid "Minimal view"
msgstr ""
#: templates/mumble/mumble.html:37
msgid "Welcome message"
msgstr "Poruka dobrodošlice"
#: templates/mumble/mumble.html:43
msgid "Server registration"
msgstr "Registracija servera"
#: templates/mumble/mumble.html:46
msgid "You are registered on this server"
msgstr "Registrirani ste na ovom serveru"
#: templates/mumble/mumble.html:48
msgid "You do not have an account on this server"
msgstr "Nemate račun na ovom serveru"
#: templates/mumble/mumble.html:57
#, python-format
msgid ""
"\n"
" <p>You need to be <a href=\"%(login_url)s\">logged in</a> to be able "
"to register an account on this Mumble server.</p>\n"
" "
msgstr ""
"\n"
" <p>Morate biti <a href=\"%(login_url)s\">prijavljeni (ulogirani)</a> "
"kako bi ste napravili račun na ovom Mumble serveru.</p>\n"
" "
#: templates/mumble/mumble.html:67
msgid ""
"\n"
" Sorry, due to a bug in Murmur 1.2.2, displaying and setting the "
"Texture is disabled.\n"
" "
msgstr ""
#: templates/mumble/mumble.html:72
#, fuzzy
msgid ""
"\n"
" You can upload an image that you would like to use as your user "
"texture here.\n"
" "
msgstr ""
"\n"
" Možete postaviti sliku za koju bi htjeli da zamjeni vašekorisničko "
"ime u Mumble transparentu (overlay).\n"
" "
#: templates/mumble/mumble.html:77
msgid "Your current texture is"
msgstr "Vaša trenutačna slika je"
#: templates/mumble/mumble.html:80
msgid "You don't currently have a texture set"
msgstr ""
#: templates/mumble/mumble.html:84
#, fuzzy
msgid ""
"\n"
" Hint: The texture image <b>needs</b> to be 600x60 in size. If "
"you upload an image with\n"
" a different size, it will be resized accordingly.<br />\n"
" "
msgstr ""
"\n"
" Savjet: Slika <b>mora</b> biti veličine 600x60. Ako odaberete "
"sliku drugačije veličine, veličina će biti promjenjena u 600x60.<br />\n"
" "
#: templates/mumble/mumble.html:103
msgid "Server administration"
msgstr "Administracija servera"
#: templates/mumble/mumble.html:117
msgid "Player"
msgstr "Korisnik"
#: templates/mumble/mumble.html:119
msgid "Online since"
msgstr "Na serveru od"
#: templates/mumble/mumble.html:120 templates/mumble/player.html:9
msgid "Authenticated"
msgstr "Registriran korisnik"
#: templates/mumble/mumble.html:121 templates/mumble/mumble.html.py:136
msgid "Admin"
msgstr "Administrator"
#: templates/mumble/mumble.html:122 templates/mumble/player.html:12
msgid "Muted"
msgstr "Utišan mikrofon"
#: templates/mumble/mumble.html:123 templates/mumble/player.html:18
msgid "Deafened"
msgstr "Utišani zvučnici / slušalice"
#: templates/mumble/mumble.html:124 templates/mumble/player.html:21
msgid "Muted by self"
msgstr "Samo-utišan mikrofon"
#: templates/mumble/mumble.html:125 templates/mumble/player.html:24
msgid "Deafened by self"
msgstr "Samo-utišani zvučnici / slušalice"
#: templates/mumble/mumble.html:127
#, fuzzy
msgid "IP Address"
msgstr "Adresa servera"
#: templates/mumble/mumble.html:131
msgid "User"
msgstr "Korisnik"
#: templates/mumble/mumble.html:134
msgid "Full Name"
msgstr "Ime i prezime"
#: templates/mumble/mumble.html:137
msgid "Sign-up date"
msgstr "Datum registracije"
#: templates/mumble/mumble.html:142
#, fuzzy
msgid "User Comment"
msgstr "Korisnički račun"
#: templates/mumble/mumble.html:154 templates/mumble/mumble.html.py:168
msgid "Kick user"
msgstr ""
#: templates/mumble/mumble.html:160
msgid "Reason"
msgstr ""
#: templates/mumble/mumble.html:165
#, fuzzy
msgid "Ban user"
msgstr "Korisnici na serveru"
#: templates/mumble/mumble.html:175
msgid "Channel"
msgstr "Soba"
#: templates/mumble/mumble.html:177
msgid "Channel ID"
msgstr "ID kanala"
#: templates/mumble/mumble.html:179
msgid "Connect"
msgstr "Spoji se"
#: templates/mumble/mumble.html:182
msgid "Channel description"
msgstr "Opis kanala"
#: templates/mumble/mumble.html:229
msgid "Delete"
msgstr ""
#: templates/mumble/mumble.html:265
msgid "Server Info"
msgstr "Informacije o serveru"
#: templates/mumble/mumble.html:266
msgid "Registration"
msgstr "Registracija"
#: templates/mumble/mumble.html:275
msgid "Administration"
msgstr "Administracija"
#: templates/mumble/mumble.html:282
msgid "User List"
msgstr "Lista korisnika"
#: templates/mumble/mumble.html:286
msgid "name"
msgstr ""
#: templates/mumble/mumble.html:306
#, fuzzy
msgid "Change password"
msgstr "Lozinka"
#: templates/mumble/mumble.html:319
msgid "Add"
msgstr ""
#: templates/mumble/mumble.html:331
msgid "Save"
msgstr ""
#: templates/mumble/mumble.html:357
msgid "Resync with Murmur"
msgstr "Resinkroniziraj s Murmurom"
#: templates/mumble/player.html:15
msgid "Suppressed"
msgstr ""
#: templates/mumble/player.html:27
msgid "has a User Comment set"
msgstr ""
#~ msgid ""
#~ "Enter the ID of the default channel here. The Channel viewer displays the "
#~ "ID to server admins on the channel detail page."
#~ msgstr ""
#~ "Unesite ID početnog kanala. Prikaz kanala pokazuje ID administratorima "
#~ "servera na stranici s detaljima kanala."
#~ msgid "The admin group was not found in the ACL's groups list!"
#~ msgstr "Grupa administratora nije pronađena u ACL listi grupa!"

Binary file not shown.

View File

@@ -0,0 +1,574 @@
# Polskie tĹumaczenia dla Mumble-Django.
#
# Copyright (C) 2009, HuNt3r <admin@serv4u.pl>
#
# Mumble-Django is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
msgid ""
msgstr ""
"Project-Id-Version: Mumble-Django v0.8\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-03-11 16:43+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: HuNt3r <admin@serv4u.pl>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: admin.py:34 admin.py:51
msgid "Master is running"
msgstr ""
#: admin.py:59 models.py:162 templates/mumble/mumble.html:28
msgid "Server Address"
msgstr "Adres serwera"
#: admin.py:66 models.py:165
msgid "Server Port"
msgstr "Port serwera"
#: admin.py:71
msgid "Instance is running"
msgstr ""
#: admin.py:81
msgid "Registered users"
msgstr "Zarejestrowani użytkownicy"
#: admin.py:91
msgid "Online users"
msgstr "Użytkownicy online"
#: admin.py:101
msgid "Channel count"
msgstr "Kanały"
#: admin.py:108
msgid "Yes"
msgstr ""
#: admin.py:110
msgid "No"
msgstr ""
#: admin.py:114
msgid "Public"
msgstr "Publiczny"
#: admin.py:132 models.py:626 templates/mumble/mumble.html:223
msgid "Admin on root channel"
msgstr "Admin na kanale głównym"
#: forms.py:83
msgid "Password required to join. Leave empty for public servers."
msgstr "Podaj hasło, lub pozostaw puste pole, aby serwer był publiczny. "
#: forms.py:86
msgid "If on, IP adresses of the clients are not logged."
msgstr "Gdy zaznaczysz, adresy IP użytkowników nie będą logowane."
#: forms.py:142
#, python-format
msgid ""
"Port number %(portno)d is not within the allowed range %(minrange)d - %"
"(maxrange)d"
msgstr "Numer portu %(portno)d jest z poza zakresu %(minrange)d - %(maxrange)d"
#: forms.py:165 templates/mumble/offline.html:12
msgid "This server is currently offline."
msgstr ""
#: forms.py:190
msgid "That name is forbidden by the server."
msgstr ""
#: forms.py:193 models.py:582
msgid "Another player already registered that name."
msgstr "Ta nazwa użytkownika jest już zajęta."
#: forms.py:201 forms.py:307 models.py:584
msgid "Cannot register player without a password!"
msgstr "Musisz podać hasło!"
#: forms.py:213 models.py:179
msgid "Server Password"
msgstr "Hasło serwera"
#: forms.py:214
msgid ""
"This server is private and protected mode is active. Please enter the server "
"password."
msgstr "Ten serwer jest prywatny. Podaj hasło aby się połączyć."
#: forms.py:222 forms.py:274
msgid "The password you entered is incorrect."
msgstr "Wprowadzone hasło jest niepoprawne"
#: forms.py:237
msgid "Link account"
msgstr ""
#: forms.py:238
msgid ""
"The account already exists and belongs to me, just link it instead of "
"creating."
msgstr ""
#: forms.py:255
msgid "No such user found."
msgstr ""
#: forms.py:290
msgid "That account belongs to someone else."
msgstr ""
#: forms.py:293
msgid "Linking Admin accounts is not allowed."
msgstr ""
#: forms.py:322 templates/mumble/mumble.html:65
#: templates/mumble/mumble.html.py:148 templates/mumble/mumble.html:278
msgid "User Texture"
msgstr "Awatar użytkownika"
#: models.py:63
msgid "DBus or ICE base"
msgstr "DBus lub baza ICE"
#: models.py:64
msgid ""
"Examples: 'net.sourceforge.mumble.murmur' for DBus or 'Meta:tcp -h 127.0.0.1 "
"-p 6502' for Ice."
msgstr ""
"Przykłąd: 'net.sourceforge.mumble.murmur' dla DBus oder 'Meta:tcp -h "
"127.0.0.1 -p 6502' dla Ice."
#: models.py:65
msgid "Ice Secret"
msgstr ""
#: models.py:68
#, fuzzy
msgid "Mumble Server"
msgstr "ID użytkownika"
#: models.py:69
#, fuzzy
msgid "Mumble Servers"
msgstr "ID użytkownika"
#: models.py:160
msgid "Server Name"
msgstr "Nazwa serwera"
#: models.py:161
msgid "Server ID"
msgstr "Serwer-ID"
#: models.py:163
msgid ""
"Hostname or IP address to bind to. You should use a hostname here, because "
"it will appear on the global server list."
msgstr ""
"Nazwa HOSTa lub adres IP. Zalecamy używać nazwy HOSTa, ponieważserwer będzie "
"widoczny na globalnej liście serwerów"
#: models.py:166
#, fuzzy
msgid "Port number to bind to. Leave empty to auto assign one."
msgstr "Numer portu. Wpisz -1 aby użyć wonego portu."
#: models.py:167
#, fuzzy
msgid "Server Display Address"
msgstr "Adres serwera"
#: models.py:168
msgid ""
"This field is only relevant if you are located behind a NAT, and names the "
"Hostname or IP address to use in the Channel Viewer and for the global "
"server list registration. If not given, the addr and port fields are used. "
"If display and bind ports are equal, you can omit it here."
msgstr ""
#: models.py:174
msgid "Superuser Password"
msgstr "Hasło SuperUser-a"
#: models.py:177
msgid "Website URL"
msgstr "Adres strony"
#: models.py:178
msgid "Welcome Message"
msgstr "Wiadomość powitalna"
#: models.py:180
msgid "Max. Users"
msgstr "Max. Użytkowników"
#: models.py:181
msgid "Bandwidth [Bps]"
msgstr "Przepustowość [Bps]"
#: models.py:182
msgid "SSL Certificate"
msgstr "SSL-Certyfikat"
#: models.py:183
msgid "SSL Key"
msgstr "SSL-Klucz"
#: models.py:184
msgid "Player name regex"
msgstr "Zabronione znaki w nazwie użytkownika"
#: models.py:185
msgid "Channel name regex"
msgstr "Zabronione znaki w nazwie kanału"
#: models.py:186
msgid "Default channel"
msgstr "Główny kanał"
#: models.py:187
msgid "Timeout"
msgstr ""
#: models.py:189
msgid "IP Obfuscation"
msgstr "IP-maskowanie"
#: models.py:190
#, fuzzy
msgid "Require Certificate"
msgstr "SSL-Certyfikat"
#: models.py:191
msgid "Maximum length of text messages"
msgstr ""
#: models.py:192
msgid "Allow HTML to be used in messages"
msgstr ""
#: models.py:193
msgid "Publish this server via Bonjour"
msgstr ""
#: models.py:194
msgid "Boot Server when Murmur starts"
msgstr ""
#: models.py:212 models.py:213
msgid "Boot Server"
msgstr "Odpal serwer"
#: models.py:217 models.py:545
msgid "Server instance"
msgstr "Aktywne serwery"
#: models.py:218
msgid "Server instances"
msgstr "Aktywne serwery"
#: models.py:505 models.py:685
msgid "This field must not be updated once the record has been saved."
msgstr "To pole nie musi być aktualizowane, było aktualizowane wcześniej."
#: models.py:542
msgid "Mumble player_id"
msgstr "ID użytkownika"
#: models.py:543
msgid "User name and Login"
msgstr "Nazwa użytkownika"
#: models.py:544
msgid "Login password"
msgstr "Hasło"
#: models.py:546 templates/mumble/mumble.html:293
msgid "Account owner"
msgstr "Właściciel konta"
#: models.py:548
#, fuzzy
msgid "The user's comment."
msgstr "Konto użytkownika"
#: models.py:549
msgid "The user's hash."
msgstr ""
#: models.py:553
msgid "User account"
msgstr "Konto użytkownika"
#: models.py:554
msgid "User accounts"
msgstr "Konta użytkowników"
#: models.py:561
#, python-format
msgid "Mumble user %(mu)s on %(srv)s owned by Django user %(du)s"
msgstr "Użytkownik %(mu)s na %(srv)s właściciel %(du)s"
#: templates/mumble/list.html:20
msgid "No server instances have been configured yet."
msgstr ""
#: templates/mumble/mumble.html:16
msgid ""
"\n"
" <b>Hint:</b><br />\n"
" This area is used to display additional information for each channel "
"and player, but requires JavaScript to be\n"
" displayed correctly. You will not see the detail pages, but you can "
"use all links and forms\n"
" that are displayed.\n"
" "
msgstr ""
"\n"
" <b>Info</b><br />\n"
" To pole jest używane do wyświetlenia dodatkowych informacji dla "
"każdegokanału i użytkownika, ale wymaga JavaScript aby\n"
" działało poprawnie. Nie zobaczysz detalów strony, lecz możesz używać "
"wszystkich linków i formatów\n"
" które zostaną wyświetlone.\n"
" "
#: templates/mumble/mumble.html:31
msgid "Website"
msgstr "Strona"
#: templates/mumble/mumble.html:33
msgid "Server version"
msgstr "Wersja serwera"
#: templates/mumble/mumble.html:34
msgid "Minimal view"
msgstr ""
#: templates/mumble/mumble.html:37
msgid "Welcome message"
msgstr "Wiadomość powitalna"
#: templates/mumble/mumble.html:43
msgid "Server registration"
msgstr "Rejestracja konta na serwerze"
#: templates/mumble/mumble.html:46
msgid "You are registered on this server"
msgstr "Masz konto na tym serwerze"
#: templates/mumble/mumble.html:48
msgid "You do not have an account on this server"
msgstr "Nie masz konta na tym serwerze"
#: templates/mumble/mumble.html:57
#, python-format
msgid ""
"\n"
" <p>You need to be <a href=\"%(login_url)s\">logged in</a> to be able "
"to register an account on this Mumble server.</p>\n"
" "
msgstr ""
"\n"
" <p>Musisz być <a href=\"%(login_url)s\">zalogowany</a> aby móc "
"zarejestrować konto na serwerze.</p>\n"
" "
#: templates/mumble/mumble.html:67
msgid ""
"\n"
" Sorry, due to a bug in Murmur 1.2.2, displaying and setting the "
"Texture is disabled.\n"
" "
msgstr ""
#: templates/mumble/mumble.html:72
#, fuzzy
msgid ""
"\n"
" You can upload an image that you would like to use as your user "
"texture here.\n"
" "
msgstr ""
"\n"
" Możesz wysłać awatar jeśli chcesz.\n"
" "
#: templates/mumble/mumble.html:77
msgid "Your current texture is"
msgstr "Twój obecny awatar"
#: templates/mumble/mumble.html:80
msgid "You don't currently have a texture set"
msgstr ""
#: templates/mumble/mumble.html:84
#, fuzzy
msgid ""
"\n"
" Hint: The texture image <b>needs</b> to be 600x60 in size. If "
"you upload an image with\n"
" a different size, it will be resized accordingly.<br />\n"
" "
msgstr ""
"\n"
" <b>Dozwolony</b> rozmiar pliku to: 600x60 pikseli. Każdy inny "
"rozmiar zostanie automatycznie zmieniony na podany wyĹĽej. "
#: templates/mumble/mumble.html:103
msgid "Server administration"
msgstr "Administracja serwerem"
#: templates/mumble/mumble.html:117
msgid "Player"
msgstr "Użytkownik"
#: templates/mumble/mumble.html:119
msgid "Online since"
msgstr "Czas online"
#: templates/mumble/mumble.html:120 templates/mumble/player.html:9
msgid "Authenticated"
msgstr "Zarejestrowany"
#: templates/mumble/mumble.html:121 templates/mumble/mumble.html.py:136
msgid "Admin"
msgstr "Administrator"
#: templates/mumble/mumble.html:122 templates/mumble/player.html:12
msgid "Muted"
msgstr "Wyciszony mikrofon"
#: templates/mumble/mumble.html:123 templates/mumble/player.html:18
msgid "Deafened"
msgstr "Wyciszone słuchawki"
#: templates/mumble/mumble.html:124 templates/mumble/player.html:21
msgid "Muted by self"
msgstr "Wyciszył mikrofon"
#: templates/mumble/mumble.html:125 templates/mumble/player.html:24
msgid "Deafened by self"
msgstr "Wyciszył słuchawki"
#: templates/mumble/mumble.html:127
#, fuzzy
msgid "IP Address"
msgstr "Adres serwera"
#: templates/mumble/mumble.html:131
msgid "User"
msgstr "Użytkownik"
#: templates/mumble/mumble.html:134
msgid "Full Name"
msgstr "Pełna nazwa"
#: templates/mumble/mumble.html:137
msgid "Sign-up date"
msgstr "Data rejestracji"
#: templates/mumble/mumble.html:142
#, fuzzy
msgid "User Comment"
msgstr "Konto użytkownika"
#: templates/mumble/mumble.html:154 templates/mumble/mumble.html.py:168
msgid "Kick user"
msgstr ""
#: templates/mumble/mumble.html:160
msgid "Reason"
msgstr ""
#: templates/mumble/mumble.html:165
#, fuzzy
msgid "Ban user"
msgstr "Użytkownicy online"
#: templates/mumble/mumble.html:175
msgid "Channel"
msgstr "Kanał"
#: templates/mumble/mumble.html:177
msgid "Channel ID"
msgstr "ID-kanału"
#: templates/mumble/mumble.html:179
msgid "Connect"
msgstr "Połącz"
#: templates/mumble/mumble.html:182
msgid "Channel description"
msgstr "Opis kanału"
#: templates/mumble/mumble.html:229
msgid "Delete"
msgstr ""
#: templates/mumble/mumble.html:265
msgid "Server Info"
msgstr "Informacje o serwerze"
#: templates/mumble/mumble.html:266
msgid "Registration"
msgstr "Rejestracja"
#: templates/mumble/mumble.html:275
msgid "Administration"
msgstr "Administracja"
#: templates/mumble/mumble.html:282
msgid "User List"
msgstr "Lista użytkowników"
#: templates/mumble/mumble.html:286
msgid "name"
msgstr ""
#: templates/mumble/mumble.html:306
#, fuzzy
msgid "Change password"
msgstr "Hasło"
#: templates/mumble/mumble.html:319
msgid "Add"
msgstr ""
#: templates/mumble/mumble.html:331
msgid "Save"
msgstr ""
#: templates/mumble/mumble.html:357
msgid "Resync with Murmur"
msgstr ""
#: templates/mumble/player.html:15
msgid "Suppressed"
msgstr ""
#: templates/mumble/player.html:27
msgid "has a User Comment set"
msgstr ""
#~ msgid ""
#~ "Enter the ID of the default channel here. The Channel viewer displays the "
#~ "ID to server admins on the channel detail page."
#~ msgstr ""
#~ "Podaj numer ID głównego kanału. Numer ID można sprawdzić w Channel-Viewer "
#~ msgid "The admin group was not found in the ACL's groups list!"
#~ msgstr "Grupa admin nie istnieje na liście grup ACL"

Binary file not shown.

View File

@@ -0,0 +1,530 @@
# German translation file for Mumble-Django.
#
# Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
#
# Mumble-Django is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
msgid ""
msgstr ""
"Project-Id-Version: Mumble-Django v0.8\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-03-14 18:23+0100\n"
"PO-Revision-Date: 2010-03-14 18:25\n"
"Last-Translator: <de@de.de>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Translated-Using: django-rosetta 0.5.3\n"
#: admin.py:34 admin.py:51
msgid "Master is running"
msgstr "Serverprozess läuft"
#: admin.py:59 models.py:162 templates/mumble/mumble.html:28
msgid "Server Address"
msgstr "Serveradresse"
#: admin.py:66 models.py:165
msgid "Server Port"
msgstr "Serverport"
#: admin.py:71
msgid "Instance is running"
msgstr "Serverinstanz läuft"
#: admin.py:81
msgid "Registered users"
msgstr "Registrierte Benutzer"
#: admin.py:91
msgid "Online users"
msgstr "Benutzer online"
#: admin.py:101
msgid "Channel count"
msgstr "Channels"
#: admin.py:108
msgid "Yes"
msgstr "Ja"
#: admin.py:110
msgid "No"
msgstr "Nein"
#: admin.py:114
msgid "Public"
msgstr "Öffentlich"
#: admin.py:132 models.py:631 templates/mumble/mumble.html:223
msgid "Admin on root channel"
msgstr "Admin im Wurzelkanal"
#: forms.py:83
msgid "Password required to join. Leave empty for public servers."
msgstr "Passwort was zum Verbinden benötigt wird. Lasse es leer, wenn der Server öffentlich sein soll."
#: forms.py:86
msgid "If on, IP adresses of the clients are not logged."
msgstr "Wenn das an ist werden IP-Adressen der Clients nicht ins Log geschrieben."
#: forms.py:142
#, python-format
msgid "Port number %(portno)d is not within the allowed range %(minrange)d - %(maxrange)d"
msgstr "Portnummer %(portno)d liegt nicht in %(minrange)d - %(maxrange)d"
#: forms.py:152
msgid "Default config"
msgstr "Standard-Einstellungen"
#: forms.py:165 templates/mumble/offline.html:12
msgid "This server is currently offline."
msgstr "Dieser Server ist im Moment nicht erreichbar."
#: forms.py:190
msgid "That name is forbidden by the server."
msgstr "Dieser Name wird vom Server nicht erlaubt."
#: forms.py:193 models.py:587
msgid "Another player already registered that name."
msgstr "Ein anderer Spieler hat sich unter diesem Namen bereits registriert."
#: forms.py:201 forms.py:307 models.py:589
msgid "Cannot register player without a password!"
msgstr "Kann Account nicht ohne Passwort registrieren!"
#: forms.py:213 models.py:179
msgid "Server Password"
msgstr "Serverpasswort"
#: forms.py:214
msgid "This server is private and protected mode is active. Please enter the server password."
msgstr "Dieser Server ist privat und der Registrierungsschutz ist aktiv. Bitte gib das Serverpasswort ein."
#: forms.py:222 forms.py:274
msgid "The password you entered is incorrect."
msgstr "Das eingegebene Passwort ist falsch"
#: forms.py:237
msgid "Link account"
msgstr "Accounts verknüpfen"
#: forms.py:238
msgid "The account already exists and belongs to me, just link it instead of creating."
msgstr "Dieser Account existiert bereits und gehört mir. Verknüpfe die Konten nur, anstatt ein neues zu erstellen."
#: forms.py:255
msgid "No such user found."
msgstr "Benutzer nicht gefunden."
#: forms.py:290
msgid "That account belongs to someone else."
msgstr "Dieser Account gehört jemand anderem."
#: forms.py:293
msgid "Linking Admin accounts is not allowed."
msgstr "Admin-Accounts zu verknüpfen ist nicht erlaubt."
#: forms.py:322 templates/mumble/mumble.html:65
#: templates/mumble/mumble.html.py:148 templates/mumble/mumble.html:278
msgid "User Texture"
msgstr "Benutzertextur"
#: models.py:63
msgid "DBus or ICE base"
msgstr "DBus- oder ICE-String"
#: models.py:64
msgid "Examples: 'net.sourceforge.mumble.murmur' for DBus or 'Meta:tcp -h 127.0.0.1 -p 6502' for Ice."
msgstr "Beispiele: 'net.sourceforge.mumble.murmur' für DBus oder 'Meta:tcp -h 127.0.0.1 -p 6502' für Ice."
#: models.py:65
msgid "Ice Secret"
msgstr "Ice-Passwort"
#: models.py:68 models.py:159
msgid "Mumble Server"
msgstr "Mumble-Server"
#: models.py:69
msgid "Mumble Servers"
msgstr "Mumble-Server"
#: models.py:160
msgid "Server Name"
msgstr "Servername"
#: models.py:161
msgid "Server ID"
msgstr "Server-ID"
#: models.py:163
msgid "Hostname or IP address to bind to. You should use a hostname here, because it will appear on the global server list."
msgstr "Hostname oder IP-Adresse unter der der Server erreichbar sein soll. Du solltest einen Hostname verwenden, da dieses Feld in der globalen Serverliste erscheint."
#: models.py:166
msgid "Port number to bind to. Leave empty to auto assign one."
msgstr "Portnummer auf die gebunden werden soll. Lasse das Feld leer um automatisch eine zuzuweisen."
#: models.py:167
msgid "Server Display Address"
msgstr "Angezeigte Adresse"
#: models.py:168
msgid "This field is only relevant if you are located behind a NAT, and names the Hostname or IP address to use in the Channel Viewer and for the global server list registration. If not given, the addr and port fields are used. If display and bind ports are equal, you can omit it here."
msgstr "Dieses Feld ist nur relevant wenn der Server hinter einem NAT steht, und gibt dann den Hostnamen oder die IP-Adresse die im Channel-Viewer und für die Registrierung in der Serverliste benutzt werden soll. Ist dieses Feld leer, werden die Angaben Addresse und Port stattdessen benutzt. Wenn die Ports identisch sind, kannst du den Port hier weglassen."
#: models.py:174
msgid "Superuser Password"
msgstr "SuperUser-Passwort"
#: models.py:177
msgid "Website URL"
msgstr "URL der Webseite"
#: models.py:178
msgid "Welcome Message"
msgstr "Willkommensnachricht"
#: models.py:180
msgid "Max. Users"
msgstr "Max. Benutzer"
#: models.py:181
msgid "Bandwidth [Bps]"
msgstr "Bandbreite [Bps]"
#: models.py:182
msgid "SSL Certificate"
msgstr "SSL-Zertifikat"
#: models.py:183
msgid "SSL Key"
msgstr "SSL-Schlüssel"
#: models.py:184
msgid "Player name regex"
msgstr "Regex für Spielernamen"
#: models.py:185
msgid "Channel name regex"
msgstr "Regex für Channelnamen"
#: models.py:186
msgid "Default channel"
msgstr "Standardchannel"
#: models.py:187
msgid "Timeout"
msgstr "Timeout"
#: models.py:189
msgid "IP Obfuscation"
msgstr "IP-Adressen anonymisieren"
#: models.py:190
msgid "Require Certificate"
msgstr "SSL-Zertifikat erzwingen"
#: models.py:191
msgid "Maximum length of text messages"
msgstr "Maximale Länge von Textnachrichten"
#: models.py:192
msgid "Allow HTML to be used in messages"
msgstr "Erlaube HTML in Nachrichten"
#: models.py:193
msgid "Publish this server via Bonjour"
msgstr "Server via Bonjour bekannt machen"
#: models.py:194
msgid "Boot Server when Murmur starts"
msgstr "Instanz starten wenn Murmur startet"
#: models.py:212 models.py:213
msgid "Boot Server"
msgstr "Server starten"
#: models.py:217 models.py:550
msgid "Server instance"
msgstr "Serverinstanz"
#: models.py:218
msgid "Server instances"
msgstr "Serverinstanzen"
#: models.py:510 models.py:690
msgid "This field must not be updated once the record has been saved."
msgstr "Dieses Feld darf nicht mehr verändert werden, nachdem der Eintrag zum ersten Mal gespeichert wurde."
#: models.py:547
msgid "Mumble player_id"
msgstr "ID des Spielers in Murmur"
#: models.py:548
msgid "User name and Login"
msgstr "Benutzername und Login"
#: models.py:549
msgid "Login password"
msgstr "Passwort"
#: models.py:551 templates/mumble/mumble.html:293
msgid "Account owner"
msgstr "Accountbesitzer"
#: models.py:553
msgid "The user's comment."
msgstr "Benutzer-Kommentar"
#: models.py:554
msgid "The user's hash."
msgstr "Signatur des Zertifikats"
#: models.py:558
msgid "User account"
msgstr "Benutzerkonto"
#: models.py:559
msgid "User accounts"
msgstr "Benutzerkonten"
#: models.py:566
#, python-format
msgid "Mumble user %(mu)s on %(srv)s owned by Django user %(du)s"
msgstr "Benutzeraccount %(mu)s auf %(srv)s mit Besitzer %(du)s"
#: templates/mumble/list.html:20
msgid "No server instances have been configured yet."
msgstr "Es sind noch keine Serverinstanzen konfiguriert."
#: templates/mumble/mumble.html:16
msgid ""
"\n"
" <b>Hint:</b><br />\n"
" This area is used to display additional information for each channel and player, but requires JavaScript to be\n"
" displayed correctly. You will not see the detail pages, but you can use all links and forms\n"
" that are displayed.\n"
" "
msgstr ""
"\n"
" <b>Hinweis:</b><br />\n"
" Dieser Bereich wird genutzt um zusätzliche Informationen über jeden Channel und Spieler anzuzeigen, erfordert aber JavaScript um\n"
" richtig angezeigt zu werden. Du wirst zwar die Detailseiten nicht sehen, kannst aber alle sichtbaren Formulare benutzen.\n"
" "
#: templates/mumble/mumble.html:31
msgid "Website"
msgstr "Webseite"
#: templates/mumble/mumble.html:33
msgid "Server version"
msgstr "Serverversion"
#: templates/mumble/mumble.html:34
msgid "Minimal view"
msgstr "Minimalansicht"
#: templates/mumble/mumble.html:37
msgid "Welcome message"
msgstr "Willkommensnachricht"
#: templates/mumble/mumble.html:43
msgid "Server registration"
msgstr "Benutzerregistrierung"
#: templates/mumble/mumble.html:46
msgid "You are registered on this server"
msgstr "Du bist auf diesem Server registriert"
#: templates/mumble/mumble.html:48
msgid "You do not have an account on this server"
msgstr "Du bist auf diesem Server nicht registriert"
#: templates/mumble/mumble.html:57
#, python-format
msgid ""
"\n"
" <p>You need to be <a href=\"%(login_url)s\">logged in</a> to be able to register an account on this Mumble server.</p>\n"
" "
msgstr ""
"\n"
" <p>Du musst <a href=\"%(login_url)s\">eingeloggt</a> sein um auf diesem Mumble-Server einen Account registrieren zu können.</p>\n"
" "
#: templates/mumble/mumble.html:67
msgid ""
"\n"
" Sorry, due to a bug in Murmur 1.2.2, displaying and setting the Texture is disabled.\n"
" "
msgstr ""
"\n"
"Entschuldigung, aufgrund eines Fehlers in Murmur 1.2.2 ist die Texturanzeige sowie das Hochladen für diese Version deaktiviert."
#: templates/mumble/mumble.html:72
msgid ""
"\n"
" You can upload an image that you would like to use as your user texture here.\n"
" "
msgstr ""
"\n"
"Du kannst hier ein Bild hochladen, das als deine Benutzertextur angezeigt werden soll."
#: templates/mumble/mumble.html:77
msgid "Your current texture is"
msgstr "Deine momentane Textur ist diese"
#: templates/mumble/mumble.html:80
msgid "You don't currently have a texture set"
msgstr "Du hast momentan keine Textur gesetzt"
#: templates/mumble/mumble.html:84
msgid ""
"\n"
" Hint: The texture image <b>needs</b> to be 600x60 in size. If you upload an image with\n"
" a different size, it will be resized accordingly.<br />\n"
" "
msgstr ""
"\n"
"Hinweis: Das Texturbild <b>muss</b> die Größe 600x60 haben. Wenn du ein Bild mit einer anderen Größe hochlädst, wird es automatisch zurecht geschnitten.<br />"
#: templates/mumble/mumble.html:103
msgid "Server administration"
msgstr "Server-Administration"
#: templates/mumble/mumble.html:117
msgid "Player"
msgstr "Spieler"
#: templates/mumble/mumble.html:119
msgid "Online since"
msgstr "Online seit"
#: templates/mumble/mumble.html:120 templates/mumble/player.html:9
msgid "Authenticated"
msgstr "Authentifiziert"
#: templates/mumble/mumble.html:121 templates/mumble/mumble.html.py:136
msgid "Admin"
msgstr "Administrator"
#: templates/mumble/mumble.html:122 templates/mumble/player.html:12
msgid "Muted"
msgstr "Stummgestellt"
#: templates/mumble/mumble.html:123 templates/mumble/player.html:18
msgid "Deafened"
msgstr "Taubgestellt"
#: templates/mumble/mumble.html:124 templates/mumble/player.html:21
msgid "Muted by self"
msgstr "Selbst stummgestellt"
#: templates/mumble/mumble.html:125 templates/mumble/player.html:24
msgid "Deafened by self"
msgstr "Selbst taubgestellt"
#: templates/mumble/mumble.html:127
msgid "IP Address"
msgstr "IP-Adresse"
#: templates/mumble/mumble.html:131
msgid "User"
msgstr "Benutzer"
#: templates/mumble/mumble.html:134
msgid "Full Name"
msgstr "Vollständiger Name"
#: templates/mumble/mumble.html:137
msgid "Sign-up date"
msgstr "Datum der Anmeldung"
#: templates/mumble/mumble.html:142
msgid "User Comment"
msgstr "Benutzer-Kommentar"
#: templates/mumble/mumble.html:154 templates/mumble/mumble.html.py:168
msgid "Kick user"
msgstr "Benutzer kicken"
#: templates/mumble/mumble.html:160
msgid "Reason"
msgstr "Begründung"
#: templates/mumble/mumble.html:165
msgid "Ban user"
msgstr "Benutzer bannen"
#: templates/mumble/mumble.html:175
msgid "Channel"
msgstr "Kanal"
#: templates/mumble/mumble.html:177
msgid "Channel ID"
msgstr "Kanal-ID"
#: templates/mumble/mumble.html:179
msgid "Connect"
msgstr "Verbinden"
#: templates/mumble/mumble.html:182
msgid "Channel description"
msgstr "Beschreibung des Kanals"
#: templates/mumble/mumble.html:229
msgid "Delete"
msgstr "Löschen"
#: templates/mumble/mumble.html:265
msgid "Server Info"
msgstr "Server-Infos"
#: templates/mumble/mumble.html:266
msgid "Registration"
msgstr "Registrierung"
#: templates/mumble/mumble.html:275
msgid "Administration"
msgstr "Administration"
#: templates/mumble/mumble.html:282
msgid "User List"
msgstr "Benutzerliste"
#: templates/mumble/mumble.html:286
msgid "name"
msgstr "Name"
#: templates/mumble/mumble.html:306
msgid "Change password"
msgstr "Passwort ändern"
#: templates/mumble/mumble.html:319
msgid "Add"
msgstr "Hinzufügen"
#: templates/mumble/mumble.html:331
msgid "Save"
msgstr "Speichern"
#: templates/mumble/mumble.html:357
msgid "Resync with Murmur"
msgstr "Liste neu laden"
#: templates/mumble/player.html:15
msgid "Suppressed"
msgstr "Unterdrückt"
#: templates/mumble/player.html:27
msgid "has a User Comment set"
msgstr "hat einen Benutzer-Kommentar gesetzt"

Binary file not shown.

View File

@@ -0,0 +1,568 @@
# French translation file for Mumble-Django.
#
# Copyright (C) 2009, Antoine Bertin <bertinantoine@neuf.fr>
#
# Mumble-Django is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
msgid ""
msgstr ""
"Project-Id-Version: Mumble-Django v2.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-03-14 18:23+0100\n"
"PO-Revision-Date: 2010-03-14 23:10+0100\n"
"Last-Translator: Antoine Bertin <bertinantoine@neuf.fr>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Translated-Using: django-rosetta 0.5.3\n"
#: admin.py:34
#: admin.py:51
msgid "Master is running"
msgstr "Le serveur fonctionne"
#: admin.py:59
#: models.py:162
#: templates/mumble/mumble.html:28
msgid "Server Address"
msgstr "Adresse du serveur"
#: admin.py:66
#: models.py:165
msgid "Server Port"
msgstr "Port du serveur"
#: admin.py:71
msgid "Instance is running"
msgstr "L'instance fonctionne"
#: admin.py:81
msgid "Registered users"
msgstr "Utilisateurs enregistrés"
#: admin.py:91
msgid "Online users"
msgstr "Utilisateurs en ligne"
#: admin.py:101
msgid "Channel count"
msgstr "Nombre de canaux"
#: admin.py:108
msgid "Yes"
msgstr "Oui"
#: admin.py:110
msgid "No"
msgstr "Non"
#: admin.py:114
msgid "Public"
msgstr "Public"
#: admin.py:132
#: models.py:631
#: templates/mumble/mumble.html:223
msgid "Admin on root channel"
msgstr "Admin sur le canal racine"
#: forms.py:83
msgid "Password required to join. Leave empty for public servers."
msgstr "Mot de passe requis pour se connecter. Laisser vide pour un serveur public."
#: forms.py:86
msgid "If on, IP adresses of the clients are not logged."
msgstr "Si oui, les adresses IP des utilisateurs ne seront pas loggées."
#: forms.py:142
#, python-format
msgid "Port number %(portno)d is not within the allowed range %(minrange)d - %(maxrange)d"
msgstr "Le port %(portno)d n'est pas dans la plage autorisée %(minrange)d - %(maxrange)d"
#: forms.py:152
msgid "Default config"
msgstr "Configuration par défaut"
#: forms.py:165
#: templates/mumble/offline.html:12
msgid "This server is currently offline."
msgstr "Ce serveur est offline."
#: forms.py:190
msgid "That name is forbidden by the server."
msgstr "Ce nom est interdit par le serveur."
#: forms.py:193
#: models.py:587
msgid "Another player already registered that name."
msgstr "Ce nom d'utilisateurs est déjà pris."
#: forms.py:201
#: forms.py:307
#: models.py:589
msgid "Cannot register player without a password!"
msgstr "Impossible d'enregistrer un utilisateur sans mot de passe!"
#: forms.py:213
#: models.py:179
msgid "Server Password"
msgstr "Mot de passe du serveur"
#: forms.py:214
msgid "This server is private and protected mode is active. Please enter the server password."
msgstr "Ce serveur est privé et le mode protégé est actif. Merci de saisir le mot de passe du serveur."
#: forms.py:222
#: forms.py:274
msgid "The password you entered is incorrect."
msgstr "Le mot de passe saisi est incorrect."
#: forms.py:237
msgid "Link account"
msgstr "Lier le compte"
#: forms.py:238
msgid "The account already exists and belongs to me, just link it instead of creating."
msgstr "Ce compte existe déjà et m'apartient. Vous pouvez le lier."
#: forms.py:255
msgid "No such user found."
msgstr "Aucun utilisateur trouvé."
#: forms.py:290
msgid "That account belongs to someone else."
msgstr "Ce compte appartient à quelqu'un d'autre."
#: forms.py:293
msgid "Linking Admin accounts is not allowed."
msgstr "Les liaisons de comptes Admin ne sont pas autorisées."
#: forms.py:322
#: templates/mumble/mumble.html:65
#: templates/mumble/mumble.html.py:148
#: templates/mumble/mumble.html:278
msgid "User Texture"
msgstr "Avatar de l'utilisateur"
#: models.py:63
msgid "DBus or ICE base"
msgstr "DBus ou ICE base"
#: models.py:64
msgid "Examples: 'net.sourceforge.mumble.murmur' for DBus or 'Meta:tcp -h 127.0.0.1 -p 6502' for Ice."
msgstr "Exemples: 'net.sourceforge.mumble.murmur' pour DBus ou 'Meta:tcp -h 127.0.0.1 -p 6502' pour Ice."
#: models.py:65
msgid "Ice Secret"
msgstr "Ice Secret"
#: models.py:68
#: models.py:159
msgid "Mumble Server"
msgstr "Serveur Mumble"
#: models.py:69
msgid "Mumble Servers"
msgstr "Serveurs Mumble"
#: models.py:160
msgid "Server Name"
msgstr "Nom du serveur"
#: models.py:161
msgid "Server ID"
msgstr "ID du serveur"
#: models.py:163
msgid "Hostname or IP address to bind to. You should use a hostname here, because it will appear on the global server list."
msgstr "Nom de domaine ou adresse IP à associer au serveur. Il est préférable d'utiliser un nom de domaine car il sera visible dans la liste des serveurs."
#: models.py:166
msgid "Port number to bind to. Leave empty to auto assign one."
msgstr "Numéro de port du serveur. Laissez vide pour assigner automatiquement."
#: models.py:167
msgid "Server Display Address"
msgstr "Adresse du serveur à afficher"
#: models.py:168
msgid "This field is only relevant if you are located behind a NAT, and names the Hostname or IP address to use in the Channel Viewer and for the global server list registration. If not given, the addr and port fields are used. If display and bind ports are equal, you can omit it here."
msgstr "Ce champ n'est valable que si vous êtes derrière un NAT. Nom de domaine ou adresse IP à utiliser dans le Channel Viewer et pour la liste des serveurs. Si vide, les champs d'adresse et de port sont utilisés. Si le port d'affichage et le port du serveur sont égaux, vous pouvez laisser vide."
#: models.py:174
msgid "Superuser Password"
msgstr "Mot de passe de Superuser"
#: models.py:177
msgid "Website URL"
msgstr "URL du site web"
#: models.py:178
msgid "Welcome Message"
msgstr "Message de bienvenue"
#: models.py:180
msgid "Max. Users"
msgstr "Utilisateurs Max."
#: models.py:181
msgid "Bandwidth [Bps]"
msgstr "Bande passante [Bps]"
#: models.py:182
msgid "SSL Certificate"
msgstr "Certificat SSL"
#: models.py:183
msgid "SSL Key"
msgstr "Clé SSL"
#: models.py:184
msgid "Player name regex"
msgstr "Regex pour le nom des utilisateurs"
#: models.py:185
msgid "Channel name regex"
msgstr "Regex pour le nom des canaux"
#: models.py:186
msgid "Default channel"
msgstr "Canal par défaut"
#: models.py:187
msgid "Timeout"
msgstr "Timeout"
#: models.py:189
msgid "IP Obfuscation"
msgstr "IP Anonyme"
#: models.py:190
msgid "Require Certificate"
msgstr "Certificat requis"
#: models.py:191
msgid "Maximum length of text messages"
msgstr "Longueur maximum des messages textes"
#: models.py:192
msgid "Allow HTML to be used in messages"
msgstr "Permettre le HTML dans les messages"
#: models.py:193
msgid "Publish this server via Bonjour"
msgstr "Publier ce serveur via Bonjour"
#: models.py:194
msgid "Boot Server when Murmur starts"
msgstr "Démarrer ce serveur au lancement de Murmur"
#: models.py:212
#: models.py:213
msgid "Boot Server"
msgstr "Serveur démarré automatiquement"
#: models.py:217
#: models.py:550
msgid "Server instance"
msgstr "Instance du serveur"
#: models.py:218
msgid "Server instances"
msgstr "Instances du serveur"
#: models.py:510
#: models.py:690
msgid "This field must not be updated once the record has been saved."
msgstr "Ce champ ne dois pas être modifié une fois l'enregistrement sauvegardé."
#: models.py:547
msgid "Mumble player_id"
msgstr "ID de l'utilisateur"
#: models.py:548
msgid "User name and Login"
msgstr "Nom d'utilisateur et login"
#: models.py:549
msgid "Login password"
msgstr "Mot de passe"
#: models.py:551
#: templates/mumble/mumble.html:293
msgid "Account owner"
msgstr "Propriétaire du compte"
#: models.py:553
msgid "The user's comment."
msgstr "Commentaire de l'utilisateur"
#: models.py:554
msgid "The user's hash."
msgstr "Hash de l'utilisateur"
#: models.py:558
msgid "User account"
msgstr "Compte d'utilisateur"
#: models.py:559
msgid "User accounts"
msgstr "Comptes d'utilisateur"
#: models.py:566
#, python-format
msgid "Mumble user %(mu)s on %(srv)s owned by Django user %(du)s"
msgstr "L'utilisateur Mumble %(mu)s sur %(srv)s correspond à l'utilisateur Django %(du)s"
#: templates/mumble/list.html:20
msgid "No server instances have been configured yet."
msgstr "Aucune instance du serveur n'a été configurée pour l'instant."
#: templates/mumble/mumble.html:16
msgid ""
"\n"
" <b>Hint:</b><br />\n"
" This area is used to display additional information for each channel and player, but requires JavaScript to be\n"
" displayed correctly. You will not see the detail pages, but you can use all links and forms\n"
" that are displayed.\n"
" "
msgstr ""
"\n"
" <b>Remarque :</b><br />\n"
" Cette zone est utilisée pour affichier des informations supplémentaires pour chaque canal et utilisateur.\n"
" JavaScript est nécessaire pour un meilleur affichage. Vous ne verrez pas les pages de détail mais vous pouvez\n"
" utiliser les liens et les formulaires.\n"
" "
#: templates/mumble/mumble.html:31
msgid "Website"
msgstr "Site web"
#: templates/mumble/mumble.html:33
msgid "Server version"
msgstr "Version du serveur"
#: templates/mumble/mumble.html:34
msgid "Minimal view"
msgstr "Vue minimaliste"
#: templates/mumble/mumble.html:37
msgid "Welcome message"
msgstr "Message de bienvenue"
#: templates/mumble/mumble.html:43
msgid "Server registration"
msgstr "Inscription serveur"
#: templates/mumble/mumble.html:46
msgid "You are registered on this server"
msgstr "Vous êtes authentifié sur ce serveur"
#: templates/mumble/mumble.html:48
msgid "You do not have an account on this server"
msgstr "Vous n'avez pas de compte sur ce serveur"
#: templates/mumble/mumble.html:57
#, python-format
msgid ""
"\n"
" <p>You need to be <a href=\"%(login_url)s\">logged in</a> to be able to register an account on this Mumble server.</p>\n"
" "
msgstr ""
"\n"
" <p>Vous devez être <a href=\"%(login_url)s\">authentifié</a> pour pouvoir inscrire un compte sur ce serveur Mumble</p>\n"
" "
#: templates/mumble/mumble.html:67
msgid ""
"\n"
" Sorry, due to a bug in Murmur 1.2.2, displaying and setting the Texture is disabled.\n"
" "
msgstr ""
"\n"
" Désolé, à cause d'un bug dans Murmur 1.2.2, l'affichage et le paramétrage des avatars sont désactivé.\n"
" "
#: templates/mumble/mumble.html:72
msgid ""
"\n"
" You can upload an image that you would like to use as your user texture here.\n"
" "
msgstr ""
"\n"
" Ici vous pouvez uploader l'avatar que vous souhaitez utiliser.\n"
" "
#: templates/mumble/mumble.html:77
msgid "Your current texture is"
msgstr "Votre avatar est"
#: templates/mumble/mumble.html:80
msgid "You don't currently have a texture set"
msgstr "Vous n'avez pas d'avatar pour l'instant"
#: templates/mumble/mumble.html:84
msgid ""
"\n"
" Hint: The texture image <b>needs</b> to be 600x60 in size. If you upload an image with\n"
" a different size, it will be resized accordingly.<br />\n"
" "
msgstr ""
"\n"
" Remarque : La taille de l'avatar <b>doit être</b> de 600x60. Si vous uploadez une image\n"
" d'une taille différente, elle sera redimensionnée.<br />\n"
" "
#: templates/mumble/mumble.html:103
msgid "Server administration"
msgstr "Administration du serveur"
#: templates/mumble/mumble.html:117
msgid "Player"
msgstr "Utilisateur"
#: templates/mumble/mumble.html:119
msgid "Online since"
msgstr "En ligne depuis"
#: templates/mumble/mumble.html:120
#: templates/mumble/player.html:9
msgid "Authenticated"
msgstr "Authentifié"
#: templates/mumble/mumble.html:121
#: templates/mumble/mumble.html.py:136
msgid "Admin"
msgstr "Admin"
#: templates/mumble/mumble.html:122
#: templates/mumble/player.html:12
msgid "Muted"
msgstr "Muet"
#: templates/mumble/mumble.html:123
#: templates/mumble/player.html:18
msgid "Deafened"
msgstr "Sourd"
#: templates/mumble/mumble.html:124
#: templates/mumble/player.html:21
msgid "Muted by self"
msgstr "Devenir muet"
#: templates/mumble/mumble.html:125
#: templates/mumble/player.html:24
msgid "Deafened by self"
msgstr "Devenir sourd"
#: templates/mumble/mumble.html:127
msgid "IP Address"
msgstr "Adresse IP du serveur"
#: templates/mumble/mumble.html:131
msgid "User"
msgstr "Utilisateur"
#: templates/mumble/mumble.html:134
msgid "Full Name"
msgstr "Nom"
#: templates/mumble/mumble.html:137
msgid "Sign-up date"
msgstr "Date d'enregistrement"
#: templates/mumble/mumble.html:142
msgid "User Comment"
msgstr "Commentaire d'utilisateur"
#: templates/mumble/mumble.html:154
#: templates/mumble/mumble.html.py:168
msgid "Kick user"
msgstr "Kicker l'utilisateur"
#: templates/mumble/mumble.html:160
msgid "Reason"
msgstr "Raison"
#: templates/mumble/mumble.html:165
msgid "Ban user"
msgstr "Bannir l'utilisateur"
#: templates/mumble/mumble.html:175
msgid "Channel"
msgstr "Canal"
#: templates/mumble/mumble.html:177
msgid "Channel ID"
msgstr "ID du canal"
#: templates/mumble/mumble.html:179
msgid "Connect"
msgstr "Se connecter"
#: templates/mumble/mumble.html:182
msgid "Channel description"
msgstr "Description du canal"
#: templates/mumble/mumble.html:229
msgid "Delete"
msgstr "Supprimer"
#: templates/mumble/mumble.html:265
msgid "Server Info"
msgstr "Information du serveur"
#: templates/mumble/mumble.html:266
msgid "Registration"
msgstr "Enregistrement"
#: templates/mumble/mumble.html:275
msgid "Administration"
msgstr "Administration"
#: templates/mumble/mumble.html:282
msgid "User List"
msgstr "Liste des utilisateurs"
#: templates/mumble/mumble.html:286
msgid "name"
msgstr "nom"
#: templates/mumble/mumble.html:306
msgid "Change password"
msgstr "Changer le mot de passe"
#: templates/mumble/mumble.html:319
msgid "Add"
msgstr "Ajouter"
#: templates/mumble/mumble.html:331
msgid "Save"
msgstr "Sauvegarder"
#: templates/mumble/mumble.html:357
msgid "Resync with Murmur"
msgstr "Synchroniser avec Murmur"
#: templates/mumble/player.html:15
msgid "Suppressed"
msgstr "Muet"
#: templates/mumble/player.html:27
msgid "has a User Comment set"
msgstr "a un commentaire"
#~ msgid "comment"
#~ msgstr "commentaire"
#~ msgid "hash"
#~ msgstr "hash"

Binary file not shown.

View File

@@ -0,0 +1,573 @@
# Italian translation file for Mumble-Django.
#
# Copyright (C) 2009, satinez <info@satinez.net>
#
# Mumble-Django is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
msgid ""
msgstr ""
"Project-Id-Version: Mumble-Django v0.8\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-03-14 18:23+0100\n"
"PO-Revision-Date: 2010-03-14 13:51\n"
"Last-Translator: <admin@admin.de>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Translated-Using: django-rosetta 0.5.3\n"
#: admin.py:34 admin.py:51
msgid "Master is running"
msgstr "Il Master è in esecuzione"
#: admin.py:59 models.py:162 templates/mumble/mumble.html:28
msgid "Server Address"
msgstr "Indirizzo del Server"
#: admin.py:66 models.py:165
msgid "Server Port"
msgstr "Port del Server"
#: admin.py:71
msgid "Instance is running"
msgstr "Istanza è in esecuzione"
#: admin.py:81
msgid "Registered users"
msgstr "Utenti registrati"
#: admin.py:91
msgid "Online users"
msgstr "Utenti online"
#: admin.py:101
msgid "Channel count"
msgstr "Quantità canali"
#: admin.py:108
msgid "Yes"
msgstr "Sì"
#: admin.py:110
msgid "No"
msgstr "No"
#: admin.py:114
msgid "Public"
msgstr "Publico"
#: admin.py:132 models.py:631 templates/mumble/mumble.html:223
msgid "Admin on root channel"
msgstr "Admin nel canale root"
#: forms.py:83
msgid "Password required to join. Leave empty for public servers."
msgstr "Password richiesta per entrare. Lasciare vuoto per i server pubblici."
#: forms.py:86
msgid "If on, IP adresses of the clients are not logged."
msgstr "Se è acceso, indirizzi IP dei clienti non vengono registrati."
#: forms.py:142
#, python-format
msgid ""
"Port number %(portno)d is not within the allowed range %(minrange)d - %"
"(maxrange)d"
msgstr ""
"Il Port %(portno)d non è consentito nel range %(minrange)d - %(maxrange)d"
#: forms.py:152
#, fuzzy
msgid "Default config"
msgstr "Canale default"
#: forms.py:165 templates/mumble/offline.html:12
msgid "This server is currently offline."
msgstr "Al momento questo server è offline."
#: forms.py:190
msgid "That name is forbidden by the server."
msgstr "Questo nome è proibito dal server."
#: forms.py:193 models.py:587
msgid "Another player already registered that name."
msgstr "Un altro giocatore è stato già sotto questo nome registrato."
#: forms.py:201 forms.py:307 models.py:589
msgid "Cannot register player without a password!"
msgstr "Non è possibile registrarsi senza una password!"
#: forms.py:213 models.py:179
msgid "Server Password"
msgstr "Server password"
#: forms.py:214
msgid ""
"This server is private and protected mode is active. Please enter the server "
"password."
msgstr ""
"Server privato, la protezione di registro è attiva. Digiti prego la password "
"d'accesso."
#: forms.py:222 forms.py:274
msgid "The password you entered is incorrect."
msgstr "La password d'accesso non è corretta."
#: forms.py:237
msgid "Link account"
msgstr "Link il conto"
#: forms.py:238
msgid ""
"The account already exists and belongs to me, just link it instead of "
"creating."
msgstr "L'account esiste già."
#: forms.py:255
msgid "No such user found."
msgstr "Nessun utente trovato."
#: forms.py:290
msgid "That account belongs to someone else."
msgstr "L'account appartiene a qualcun altro."
#: forms.py:293
msgid "Linking Admin accounts is not allowed."
msgstr "Collegare account Amministratore non è permesso."
#: forms.py:322 templates/mumble/mumble.html:65
#: templates/mumble/mumble.html.py:148 templates/mumble/mumble.html:278
msgid "User Texture"
msgstr "Immagine del Utente"
#: models.py:63
msgid "DBus or ICE base"
msgstr "DBus- o ICE-base"
#: models.py:64
msgid ""
"Examples: 'net.sourceforge.mumble.murmur' for DBus or 'Meta:tcp -h 127.0.0.1 "
"-p 6502' for Ice."
msgstr ""
"Esempi: 'net.sourceforge.mumble.murmur' per DBus o 'Meta: tcp-h 127.0.0.1-p "
"6502' per ICE."
#: models.py:65
msgid "Ice Secret"
msgstr "ICE Secret"
#: models.py:68 models.py:159
msgid "Mumble Server"
msgstr "Mumble Server"
#: models.py:69
msgid "Mumble Servers"
msgstr "Mumble Servers"
#: models.py:160
msgid "Server Name"
msgstr "Nome del Server"
#: models.py:161
msgid "Server ID"
msgstr "Server-ID"
#: models.py:163
msgid ""
"Hostname or IP address to bind to. You should use a hostname here, because "
"it will appear on the global server list."
msgstr ""
"Hostname o l'indirizzo IP da associare. Si dovrebbe usare un hostname qui, "
"perché sarà visualizzato nella lista globale dei server."
#: models.py:166
msgid "Port number to bind to. Leave empty to auto assign one."
msgstr "Numero del Port da associare. Lasciare vuoto per auto-assegnare uno."
#: models.py:167
msgid "Server Display Address"
msgstr "l'indirizzo Server display"
#: models.py:168
msgid ""
"This field is only relevant if you are located behind a NAT, and names the "
"Hostname or IP address to use in the Channel Viewer and for the global "
"server list registration. If not given, the addr and port fields are used. "
"If display and bind ports are equal, you can omit it here."
msgstr ""
"Questo campo è rilevante solo se si trova dietro un NAT, nomi e il Hostname "
"o indirizzi IP da utilizzare nel canale Viewer e per la lista globale dei "
"Server. \n"
"\n"
"Se il Port del display e il Port del bind sono uguali, è possibile omettere "
"qui."
#: models.py:174
msgid "Superuser Password"
msgstr "SuperUser password"
#: models.py:177
msgid "Website URL"
msgstr "URL del sito web"
#: models.py:178
msgid "Welcome Message"
msgstr "Messaggio di benvenuto"
#: models.py:180
msgid "Max. Users"
msgstr "Max. Utenti"
#: models.py:181
msgid "Bandwidth [Bps]"
msgstr "larghezza di banda [Bps]"
#: models.py:182
msgid "SSL Certificate"
msgstr "SSL-Certificato"
#: models.py:183
msgid "SSL Key"
msgstr "SSL-Chiave"
#: models.py:184
msgid "Player name regex"
msgstr "Regex per giocatori"
#: models.py:185
msgid "Channel name regex"
msgstr "Regex per Canali"
#: models.py:186
msgid "Default channel"
msgstr "Canale default"
#: models.py:187
msgid "Timeout"
msgstr "Timeout"
#: models.py:189
msgid "IP Obfuscation"
msgstr "Anonimizza indirizzo IP"
#: models.py:190
msgid "Require Certificate"
msgstr "Serve il Certificate"
#: models.py:191
msgid "Maximum length of text messages"
msgstr "La lunghezza massima dei messaggi"
#: models.py:192
msgid "Allow HTML to be used in messages"
msgstr "Permettere HTML nei messaggi"
#: models.py:193
msgid "Publish this server via Bonjour"
msgstr "Pubblica questo server tramite Bonjour"
#: models.py:194
msgid "Boot Server when Murmur starts"
msgstr "Boot Server quando si avvia Murmur"
#: models.py:212 models.py:213
msgid "Boot Server"
msgstr "Boot Server"
#: models.py:217 models.py:550
msgid "Server instance"
msgstr "Istanza di server"
#: models.py:218
msgid "Server instances"
msgstr "Istanze di server"
#: models.py:510 models.py:690
msgid "This field must not be updated once the record has been saved."
msgstr ""
"Questo campo non si puo cambiare, dopo che é stato salvato per la prima "
"volta."
#: models.py:547
msgid "Mumble player_id"
msgstr "ID del giocatore in Murmur"
#: models.py:548
msgid "User name and Login"
msgstr "Nome Utente e Login"
#: models.py:549
msgid "Login password"
msgstr "Login password"
#: models.py:551 templates/mumble/mumble.html:293
msgid "Account owner"
msgstr "Proprietario del account"
#: models.py:553
msgid "The user's comment."
msgstr "Commento dell'utente."
#: models.py:554
msgid "The user's hash."
msgstr "Hash dell'utente."
#: models.py:558
msgid "User account"
msgstr "Account del utente"
#: models.py:559
msgid "User accounts"
msgstr "Accounts degli utenti"
#: models.py:566
#, python-format
msgid "Mumble user %(mu)s on %(srv)s owned by Django user %(du)s"
msgstr "Account %(mu)s su %(srv)s con Utente %(du)s"
#: templates/mumble/list.html:20
msgid "No server instances have been configured yet."
msgstr "Nessune istanze del server sono stati configurati."
#: templates/mumble/mumble.html:16
msgid ""
"\n"
" <b>Hint:</b><br />\n"
" This area is used to display additional information for each channel "
"and player, but requires JavaScript to be\n"
" displayed correctly. You will not see the detail pages, but you can "
"use all links and forms\n"
" that are displayed.\n"
" "
msgstr ""
"\n"
" <b>Nota</b><br />\n"
" Questo settore è utilizzato per visualizzare informazioni aggiuntive "
"per ogni canale e giocatore, ma richiede JavaScript per essere\n"
" visualizzato correttamente. Non vedi il dettaglio delle pagine, ma "
"puoi utilizzare tutti i link e le forme\n"
" che vengono visualizzati.\n"
" "
#: templates/mumble/mumble.html:31
msgid "Website"
msgstr "Sito web"
#: templates/mumble/mumble.html:33
msgid "Server version"
msgstr "Versione del Server"
#: templates/mumble/mumble.html:34
msgid "Minimal view"
msgstr "La visualizzazione minimale"
#: templates/mumble/mumble.html:37
msgid "Welcome message"
msgstr "Messaggio di benvenuto"
#: templates/mumble/mumble.html:43
msgid "Server registration"
msgstr "Registrazione Server"
#: templates/mumble/mumble.html:46
msgid "You are registered on this server"
msgstr "Sei registrato su questo Server "
#: templates/mumble/mumble.html:48
msgid "You do not have an account on this server"
msgstr "Non sei registrato su questo Server "
#: templates/mumble/mumble.html:57
#, python-format
msgid ""
"\n"
" <p>You need to be <a href=\"%(login_url)s\">logged in</a> to be able "
"to register an account on this Mumble server.</p>\n"
" "
msgstr ""
"\n"
" <p>Devi avere un <a href=\"%(login_url)s\">login</a> per registrarti "
"su questo mumble server</p>\n"
" "
#: templates/mumble/mumble.html:67
msgid ""
"\n"
" Sorry, due to a bug in Murmur 1.2.2, displaying and setting the "
"Texture is disabled.\n"
" "
msgstr ""
"\n"
"Scusa, a causa di un bug in Murmur 1.2.2, la visualizzazione e "
"l'impostazione è disattivato."
#: templates/mumble/mumble.html:72
msgid ""
"\n"
" You can upload an image that you would like to use as your user "
"texture here.\n"
" "
msgstr ""
"\n"
"È possibile caricare l'immagine che si desidera utilizzare come texture "
"utente."
#: templates/mumble/mumble.html:77
msgid "Your current texture is"
msgstr "Il tuo attuale immagine"
#: templates/mumble/mumble.html:80
msgid "You don't currently have a texture set"
msgstr "Al momento non hai dei texture set"
#: templates/mumble/mumble.html:84
msgid ""
"\n"
" Hint: The texture image <b>needs</b> to be 600x60 in size. If "
"you upload an image with\n"
" a different size, it will be resized accordingly.<br />\n"
" "
msgstr ""
"\n"
"Nota! La immagine <b>deve avere</b> le dimensioni di 600x60 pixel. Se hai "
"un'immagine con una dimensione diversa, si aggiusta automaticamente. "
#: templates/mumble/mumble.html:103
msgid "Server administration"
msgstr "Amministrazione del Server"
#: templates/mumble/mumble.html:117
msgid "Player"
msgstr "Giocatore"
#: templates/mumble/mumble.html:119
msgid "Online since"
msgstr "Online da"
#: templates/mumble/mumble.html:120 templates/mumble/player.html:9
msgid "Authenticated"
msgstr "Authenticated"
#: templates/mumble/mumble.html:121 templates/mumble/mumble.html.py:136
msgid "Admin"
msgstr "Amministratore"
#: templates/mumble/mumble.html:122 templates/mumble/player.html:12
msgid "Muted"
msgstr "Muto"
#: templates/mumble/mumble.html:123 templates/mumble/player.html:18
msgid "Deafened"
msgstr "Sordo"
#: templates/mumble/mumble.html:124 templates/mumble/player.html:21
msgid "Muted by self"
msgstr "Mutato solo"
#: templates/mumble/mumble.html:125 templates/mumble/player.html:24
msgid "Deafened by self"
msgstr "Rintronarsi"
#: templates/mumble/mumble.html:127
msgid "IP Address"
msgstr "Indirizzo IP del Server"
#: templates/mumble/mumble.html:131
msgid "User"
msgstr "Utente"
#: templates/mumble/mumble.html:134
msgid "Full Name"
msgstr "Nome"
#: templates/mumble/mumble.html:137
msgid "Sign-up date"
msgstr "Data di registrazione"
#: templates/mumble/mumble.html:142
msgid "User Comment"
msgstr "User Comment"
#: templates/mumble/mumble.html:154 templates/mumble/mumble.html.py:168
msgid "Kick user"
msgstr "Kick utente"
#: templates/mumble/mumble.html:160
msgid "Reason"
msgstr "Diritto"
#: templates/mumble/mumble.html:165
msgid "Ban user"
msgstr "Ban utenti"
#: templates/mumble/mumble.html:175
msgid "Channel"
msgstr "Canale"
#: templates/mumble/mumble.html:177
msgid "Channel ID"
msgstr "ID del Canale"
#: templates/mumble/mumble.html:179
msgid "Connect"
msgstr "Connect"
#: templates/mumble/mumble.html:182
msgid "Channel description"
msgstr "descrizione del canale"
#: templates/mumble/mumble.html:229
msgid "Delete"
msgstr "Cancellare"
#: templates/mumble/mumble.html:265
msgid "Server Info"
msgstr "Informazioni del Server"
#: templates/mumble/mumble.html:266
msgid "Registration"
msgstr "Registrazione"
#: templates/mumble/mumble.html:275
msgid "Administration"
msgstr "Amministrazione"
#: templates/mumble/mumble.html:282
msgid "User List"
msgstr "Lista dei utenti"
#: templates/mumble/mumble.html:286
msgid "name"
msgstr "Nome"
#: templates/mumble/mumble.html:306
msgid "Change password"
msgstr "Cambia la password"
#: templates/mumble/mumble.html:319
msgid "Add"
msgstr "Aggiungere"
#: templates/mumble/mumble.html:331
msgid "Save"
msgstr "Salva"
#: templates/mumble/mumble.html:357
msgid "Resync with Murmur"
msgstr "Sincronizza con Murmur"
#: templates/mumble/player.html:15
msgid "Suppressed"
msgstr "Soppresso"
#: templates/mumble/player.html:27
msgid "has a User Comment set"
msgstr "Ha un commento del Utente set"

Binary file not shown.

View File

@@ -0,0 +1,543 @@
# Japanese translation file for Mumble-Django.
#
# Copyright (C) 2009, withgod <noname@withgod.jp>
# Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
#
# Mumble-Django is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
msgid ""
msgstr ""
"Project-Id-Version: Mumble-Django v0.8\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-03-14 18:23+0100\n"
"PO-Revision-Date: 2010-03-15 21:47\n"
"Last-Translator: <nocontents@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Translated-Using: django-rosetta 0.5.2\n"
#: admin.py:34 admin.py:51
msgid "Master is running"
msgstr "マスター実行中"
#: admin.py:59 models.py:162 templates/mumble/mumble.html:28
msgid "Server Address"
msgstr "サーバアドレス"
#: admin.py:66 models.py:165
msgid "Server Port"
msgstr "サーバポート"
#: admin.py:71
msgid "Instance is running"
msgstr "インスタンス実行中"
#: admin.py:81
msgid "Registered users"
msgstr "ユーザ登録"
#: admin.py:91
msgid "Online users"
msgstr "オンラインのユーザ"
#: admin.py:101
msgid "Channel count"
msgstr "チャンネル数"
#: admin.py:108
msgid "Yes"
msgstr "はい"
#: admin.py:110
msgid "No"
msgstr "いいえ"
#: admin.py:114
msgid "Public"
msgstr "公開"
#: admin.py:132 models.py:631 templates/mumble/mumble.html:223
msgid "Admin on root channel"
msgstr "管理者権限を与える"
#: forms.py:83
msgid "Password required to join. Leave empty for public servers."
msgstr "サーバに参加するにはパスワードが必要。空白にすると公開サーバになります。"
#: forms.py:86
msgid "If on, IP adresses of the clients are not logged."
msgstr "If on, IP adresses of the clients are not logged."
#: forms.py:142
#, python-format
msgid "Port number %(portno)d is not within the allowed range %(minrange)d - %(maxrange)d"
msgstr "ポート番号 %(portno)d は許可されていません、次の範囲が許可されています %(minrange)d - %(maxrange)d"
#: forms.py:152
msgid "Default config"
msgstr "デフォルトコンフィグ"
#: forms.py:165 templates/mumble/offline.html:12
msgid "This server is currently offline."
msgstr "このサーバーは現在停止中です。"
#: forms.py:190
msgid "That name is forbidden by the server."
msgstr "その名前はサーバによって不可視にされています"
#: forms.py:193 models.py:587
msgid "Another player already registered that name."
msgstr "その名前は既に使われています."
#: forms.py:201 forms.py:307 models.py:589
msgid "Cannot register player without a password!"
msgstr "パスワードは必須です"
#: forms.py:213 models.py:179
msgid "Server Password"
msgstr "サーバパスワード"
#: forms.py:214
msgid "This server is private and protected mode is active. Please enter the server password."
msgstr "本サーバをプライベート及びプロテクトモードで利用する場合は、パスワードを入力してください。"
#: forms.py:222 forms.py:274
msgid "The password you entered is incorrect."
msgstr "パスワードが不正です。"
#: forms.py:237
msgid "Link account"
msgstr "アカウントリンク"
#: forms.py:238
msgid "The account already exists and belongs to me, just link it instead of creating."
msgstr "アカウントは既に存在していて、自分にリンクしています。リンクの変わりに作成して下さい。"
#: forms.py:255
msgid "No such user found."
msgstr "ユーザが見つかりません"
#: forms.py:290
msgid "That account belongs to someone else."
msgstr "このアカウントは既に他のアカウントにリンクされています。"
#: forms.py:293
msgid "Linking Admin accounts is not allowed."
msgstr "管理者アカウントへのリンクは許可されていません"
#: forms.py:322 templates/mumble/mumble.html:65
#: templates/mumble/mumble.html.py:148 templates/mumble/mumble.html:278
msgid "User Texture"
msgstr "ユーザ画像"
#: models.py:63
msgid "DBus or ICE base"
msgstr "DBusもしくはICE"
#: models.py:64
msgid "Examples: 'net.sourceforge.mumble.murmur' for DBus or 'Meta:tcp -h 127.0.0.1 -p 6502' for Ice."
msgstr "例: DBusなら'net.sourceforge.mumble.murmur' Iceなら'Meta:tcp -h 127.0.0.1 -p 6502'"
#: models.py:65
msgid "Ice Secret"
msgstr "Ice Secret"
#: models.py:68 models.py:159
msgid "Mumble Server"
msgstr "マンブルユーザID"
#: models.py:69
msgid "Mumble Servers"
msgstr "マンブルユーザID"
#: models.py:160
msgid "Server Name"
msgstr "サーバ名"
#: models.py:161
msgid "Server ID"
msgstr "サーバID"
#: models.py:163
msgid "Hostname or IP address to bind to. You should use a hostname here, because it will appear on the global server list."
msgstr "利用するホスト名もしくはIPアドレス. ホスト名も利用することが出来ます。 サーバ一覧に表示されます。."
#: models.py:166
msgid "Port number to bind to. Leave empty to auto assign one."
msgstr "利用するポート番号を指定します、未指定の場合空いているポートを利用します。"
#: models.py:167
msgid "Server Display Address"
msgstr "サーバアドレス"
#: models.py:168
msgid "This field is only relevant if you are located behind a NAT, and names the Hostname or IP address to use in the Channel Viewer and for the global server list registration. If not given, the addr and port fields are used. If display and bind ports are equal, you can omit it here."
msgstr "本項目は、NATの内側等にあるサーバに適切です。ホスト名もしくはIPアドレスを、チャンネルビューアや、サーバリスト登録に使います。 "
#: models.py:174
msgid "Superuser Password"
msgstr "管理者パスワード"
#: models.py:177
msgid "Website URL"
msgstr "ウェブサイトのURL"
#: models.py:178
msgid "Welcome Message"
msgstr "サーバメッセージ"
#: models.py:180
msgid "Max. Users"
msgstr "最大ユーザ数"
#: models.py:181
msgid "Bandwidth [Bps]"
msgstr "帯域制限 [Bps]"
#: models.py:182
msgid "SSL Certificate"
msgstr "SSL認証"
#: models.py:183
msgid "SSL Key"
msgstr "SSL認証鍵"
#: models.py:184
msgid "Player name regex"
msgstr "ユーザ名の正規表現制限"
#: models.py:185
msgid "Channel name regex"
msgstr "チャンネル名の正規表現制限"
#: models.py:186
msgid "Default channel"
msgstr "デフォルトチャンネル"
#: models.py:187
msgid "Timeout"
msgstr "タイムアウト"
#: models.py:189
msgid "IP Obfuscation"
msgstr "IPアドレス隠蔽"
#: models.py:190
msgid "Require Certificate"
msgstr "SSL認証"
#: models.py:191
msgid "Maximum length of text messages"
msgstr "テキストメッセージの最大文字数"
#: models.py:192
msgid "Allow HTML to be used in messages"
msgstr "HTMLメッセージを許可する"
#: models.py:193
msgid "Publish this server via Bonjour"
msgstr "Bonjourでこのサーバを公開する"
#: models.py:194
msgid "Boot Server when Murmur starts"
msgstr "Murmur起動時にサーバを起動します"
#: models.py:212 models.py:213
msgid "Boot Server"
msgstr "サーバ起動"
#: models.py:217 models.py:550
msgid "Server instance"
msgstr "サーバインスタンス"
#: models.py:218
msgid "Server instances"
msgstr "全サーバインスタンス"
#: models.py:510 models.py:690
msgid "This field must not be updated once the record has been saved."
msgstr "この項目はアップデート出来ません."
#: models.py:547
msgid "Mumble player_id"
msgstr "マンブルユーザID"
#: models.py:548
msgid "User name and Login"
msgstr "ユーザID"
#: models.py:549
msgid "Login password"
msgstr "パスワード"
#: models.py:551 templates/mumble/mumble.html:293
msgid "Account owner"
msgstr "アカウントの所有者"
#: models.py:553
msgid "The user's comment."
msgstr "ユーザコメント"
#: models.py:554
msgid "The user's hash."
msgstr "ユーザのハッシュ"
#: models.py:558
msgid "User account"
msgstr "ユーザアカウント"
#: models.py:559
msgid "User accounts"
msgstr "全ユーザアカウント"
#: models.py:566
#, python-format
msgid "Mumble user %(mu)s on %(srv)s owned by Django user %(du)s"
msgstr "マンブルユーザ %(mu)s は %(srv)s のDjangoユーザ %(du)s です"
#: templates/mumble/list.html:20
msgid "No server instances have been configured yet."
msgstr "サーバ設定が存在しません"
#: templates/mumble/mumble.html:16
msgid ""
"\n"
" <b>Hint:</b><br />\n"
" This area is used to display additional information for each channel and player, but requires JavaScript to be\n"
" displayed correctly. You will not see the detail pages, but you can use all links and forms\n"
" that are displayed.\n"
" "
msgstr ""
"\n"
" <b>ヒント:</b><br />\n"
" このエリアはチャンネル情報をやプレーヤー情報を表示するのにjavascriptが必要です\n"
" javascriptをオンにしないと、すべての情報を閲覧することは出来ません。 "
#: templates/mumble/mumble.html:31
msgid "Website"
msgstr "ウェブサイト"
#: templates/mumble/mumble.html:33
msgid "Server version"
msgstr "サーババージョン"
#: templates/mumble/mumble.html:34
msgid "Minimal view"
msgstr "縮小表示"
#: templates/mumble/mumble.html:37
msgid "Welcome message"
msgstr "サーバメッセージ"
#: templates/mumble/mumble.html:43
msgid "Server registration"
msgstr "サーバ登録"
#: templates/mumble/mumble.html:46
msgid "You are registered on this server"
msgstr "アカウントは既に登録されています"
#: templates/mumble/mumble.html:48
msgid "You do not have an account on this server"
msgstr "アカウントは登録されていません"
#: templates/mumble/mumble.html:57
#, python-format
msgid ""
"\n"
" <p>You need to be <a href=\"%(login_url)s\">logged in</a> to be able to register an account on this Mumble server.</p>\n"
" "
msgstr ""
"\n"
" <p><a href=\"%(login_url)s\">ログイン</a>してから ユーザをこのMumbleサーバに登録する必要があります</p>\n"
" "
#: templates/mumble/mumble.html:67
msgid ""
"\n"
" Sorry, due to a bug in Murmur 1.2.2, displaying and setting the Texture is disabled.\n"
" "
msgstr ""
"\n"
"1.2.2 ではユーザ画像の表示がバグってるため、ユーザ画像表示は無効です。"
#: templates/mumble/mumble.html:72
msgid ""
"\n"
" You can upload an image that you would like to use as your user texture here.\n"
" "
msgstr ""
"\n"
"任意のユーザ画像をここから登録することがきます。"
#: templates/mumble/mumble.html:77
msgid "Your current texture is"
msgstr "現在の画像"
#: templates/mumble/mumble.html:80
msgid "You don't currently have a texture set"
msgstr "貴方は画像を持っていません"
#: templates/mumble/mumble.html:84
msgid ""
"\n"
" Hint: The texture image <b>needs</b> to be 600x60 in size. If you upload an image with\n"
" a different size, it will be resized accordingly.<br />\n"
" "
msgstr ""
"\n"
"ヒント: 画像は600x60である<b>必要</b>があります。そのサイズを超えたり収まらない 場合は、リサイズが行われます。 <br />\n"
" "
#: templates/mumble/mumble.html:103
msgid "Server administration"
msgstr "サーバ管理"
#: templates/mumble/mumble.html:117
msgid "Player"
msgstr "プレーヤ"
#: templates/mumble/mumble.html:119
msgid "Online since"
msgstr "接続開始時間"
#: templates/mumble/mumble.html:120 templates/mumble/player.html:9
msgid "Authenticated"
msgstr "登録済み"
#: templates/mumble/mumble.html:121 templates/mumble/mumble.html.py:136
msgid "Admin"
msgstr "管理者"
#: templates/mumble/mumble.html:122 templates/mumble/player.html:12
msgid "Muted"
msgstr "発言禁止"
#: templates/mumble/mumble.html:123 templates/mumble/player.html:18
msgid "Deafened"
msgstr "聴取禁止"
#: templates/mumble/mumble.html:124 templates/mumble/player.html:21
msgid "Muted by self"
msgstr "自分で発言禁止"
#: templates/mumble/mumble.html:125 templates/mumble/player.html:24
msgid "Deafened by self"
msgstr "自分で聴取禁止"
#: templates/mumble/mumble.html:127
msgid "IP Address"
msgstr "IPアドレス"
#: templates/mumble/mumble.html:131
msgid "User"
msgstr "ユーザ"
#: templates/mumble/mumble.html:134
msgid "Full Name"
msgstr "名前"
#: templates/mumble/mumble.html:137
msgid "Sign-up date"
msgstr "登録日"
#: templates/mumble/mumble.html:142
msgid "User Comment"
msgstr "ユーザアカウント"
#: templates/mumble/mumble.html:154 templates/mumble/mumble.html.py:168
msgid "Kick user"
msgstr "ユーザをキック"
#: templates/mumble/mumble.html:160
msgid "Reason"
msgstr "意味"
#: templates/mumble/mumble.html:165
msgid "Ban user"
msgstr "ユーザを追放"
#: templates/mumble/mumble.html:175
msgid "Channel"
msgstr "チャンネル"
#: templates/mumble/mumble.html:177
msgid "Channel ID"
msgstr "チャンネルID"
#: templates/mumble/mumble.html:179
msgid "Connect"
msgstr "接続"
#: templates/mumble/mumble.html:182
msgid "Channel description"
msgstr "チャンネル説明"
#: templates/mumble/mumble.html:229
msgid "Delete"
msgstr "削除"
#: templates/mumble/mumble.html:265
msgid "Server Info"
msgstr "サーバ情報"
#: templates/mumble/mumble.html:266
msgid "Registration"
msgstr "登録"
#: templates/mumble/mumble.html:275
msgid "Administration"
msgstr "管理"
#: templates/mumble/mumble.html:282
msgid "User List"
msgstr "ユーザ一覧"
#: templates/mumble/mumble.html:286
msgid "name"
msgstr "名前"
#: templates/mumble/mumble.html:306
msgid "Change password"
msgstr "パスワード"
#: templates/mumble/mumble.html:319
msgid "Add"
msgstr "追加"
#: templates/mumble/mumble.html:331
msgid "Save"
msgstr "セーブ"
#: templates/mumble/mumble.html:357
msgid "Resync with Murmur"
msgstr "Murmurと再同期"
#: templates/mumble/player.html:15
msgid "Suppressed"
msgstr "鎮められた"
#: templates/mumble/player.html:27
msgid "has a User Comment set"
msgstr "コメントをセットされています"
#~ msgid "comment"
#~ msgstr "コメント"
#~ msgid "hash"
#~ msgstr "ハッシュ"
#~ msgid "Enter the ID of the default channel here. The Channel viewer displays the ID to server admins on the channel detail page."
#~ msgstr "接続開始時に利用されるデフォルトチャンネルIDを指定して下さい。チャンネル表示一覧にて チャンネル詳細を確認すればチャンネルIDを調べられます。."
#~ msgid "The admin group was not found in the ACL's groups list!"
#~ msgstr "管理者グループが権限一覧に見当たりません."

View File

@@ -14,9 +14,43 @@
* GNU General Public License for more details. * GNU General Public License for more details.
""" """
from server_detect import find_existing_instances from shutil import copy, move
from os.path import exists, join
from django.conf import settings
from django.db import connection
from django.db.models import signals from django.db.models import signals
from mumble import models from mumble import models
from update_schema import update_schema
from server_detect import find_existing_instances
if settings.DATABASE_ENGINE == "sqlite3":
# Move the DB to the db subdirectory if necessary.
oldpath = join( settings.MUMBLE_DJANGO_ROOT, "mumble-django.db3" )
if not exists( settings.DATABASE_NAME ) and exists( oldpath ):
move( oldpath, settings.DATABASE_NAME )
cursor = connection.cursor()
tablename = models.Mumble._meta.db_table
if tablename in connection.introspection.get_table_list(cursor):
fields = connection.introspection.get_table_description(cursor, tablename)
uptodate = "server_id" in [ entry[0] for entry in fields ]
else:
# Table doesn't yet exist, so syncdb will create it properly
uptodate = True
if not uptodate:
if settings.DATABASE_ENGINE == "sqlite3":
# backup the db before the conversion.
copy( settings.DATABASE_NAME, settings.DATABASE_NAME+".bak" )
signals.post_syncdb.connect( update_schema, sender=models );
else:
signals.post_syncdb.connect( find_existing_instances, sender=models ); signals.post_syncdb.connect( find_existing_instances, sender=models );

View File

@@ -14,7 +14,7 @@
* GNU General Public License for more details. * GNU General Public License for more details.
""" """
import os, Ice import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.contrib.auth.models import User from django.contrib.auth.models import User
@@ -28,8 +28,16 @@ class TestFailed( Exception ):
pass; pass;
class Command( BaseCommand ): class Command( BaseCommand ):
help = "Run a few tests on Mumble-Django's setup."
def handle(self, **options): def handle(self, **options):
try:
import Ice
except ImportError:
pass
else:
self.check_slice(); self.check_slice();
self.check_rootdir(); self.check_rootdir();
self.check_dbase(); self.check_dbase();
self.check_sites(); self.check_sites();
@@ -166,10 +174,10 @@ class Command( BaseCommand ):
else: else:
for mumble in mm: for mumble in mm:
try: try:
mumble.getCtl(); mumble.ctl
except Ice.Exception, err: except Exception, err:
raise TestFailed( raise TestFailed(
"Connecting to Murmur `%s` (%s) failed: %s" % ( mumble.name, mumble.dbus, err ) "Connecting to Murmur `%s` (%s) failed: %s" % ( mumble.name, mumble.server, err )
); );
print "[ OK ]"; print "[ OK ]";

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
"""
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
*
* Mumble-Django is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
"""
import Ice, IcePy, os, getpass
from sys import stderr
from django.core.management.base import BaseCommand
from mumble.models import MumbleServer
class Command( BaseCommand ):
help = "Check if the known servers support getSlice."
def handle(self, **options):
prop = Ice.createProperties([])
prop.setProperty("Ice.ImplicitContext", "Shared")
idd = Ice.InitializationData()
idd.properties = prop
ice = Ice.initialize(idd)
for serv in MumbleServer.objects.all():
print "Probing server at '%s'..." % serv.dbus
if serv.secret:
ice.getImplicitContext().put( "secret", serv.secret.encode("utf-8") )
prx = ice.stringToProxy( serv.dbus.encode("utf-8") )
# Try loading the Slice from Murmur directly via its getSlice method.
try:
slice = IcePy.Operation( 'getSlice',
Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent,
True, (), (), (), IcePy._t_string, ()
).invoke(prx, ((), None))
except TypeError, err:
print " Received TypeError:", err
print " It seems your version of IcePy is incompatible."
except Ice.OperationNotExistException:
print " Your version of Murmur does not support getSlice."
else:
print " Successfully received the slice (length: %d bytes.)" % len(slice)

View File

@@ -14,11 +14,12 @@
* GNU General Public License for more details. * GNU General Public License for more details.
""" """
import os import os, getpass
from django.db import DatabaseError
from django.conf import settings from django.conf import settings
from mumble import models from mumble.models import MumbleServer, Mumble
from mumble.mctl import MumbleCtlBase from mumble.mctl import MumbleCtlBase
@@ -61,7 +62,7 @@ def find_existing_instances( **kwargs ):
print " 2) ICE -- Meta:tcp -h 127.0.0.1 -p 6502" print " 2) ICE -- Meta:tcp -h 127.0.0.1 -p 6502"
print "Enter 1 or 2 for the defaults above, nothing to skip Server detection," print "Enter 1 or 2 for the defaults above, nothing to skip Server detection,"
print "and if the defaults do not fit your needs, enter the correct string." print "and if the defaults do not fit your needs, enter the correct string."
print "Whether to use DBus or ICE will be detected automatically from the" print "Whether to use DBus or Ice will be detected automatically from the"
print "string's format." print "string's format."
print print
@@ -78,8 +79,10 @@ def find_existing_instances( **kwargs ):
elif dbusName == "2": elif dbusName == "2":
dbusName = "Meta:tcp -h 127.0.0.1 -p 6502"; dbusName = "Meta:tcp -h 127.0.0.1 -p 6502";
icesecret = getpass.getpass("Please enter the Ice secret (if any): ");
try: try:
ctl = MumbleCtlBase.newInstance( dbusName, settings.SLICE ); ctl = MumbleCtlBase.newInstance( dbusName, settings.SLICE, icesecret );
except Exception, instance: except Exception, instance:
if v: if v:
print "Unable to connect using name %s. The error was:" % dbusName; print "Unable to connect using name %s. The error was:" % dbusName;
@@ -92,40 +95,71 @@ def find_existing_instances( **kwargs ):
servIDs = ctl.getAllServers(); servIDs = ctl.getAllServers();
try:
meta = MumbleServer.objects.get( dbus=dbusName );
except MumbleServer.DoesNotExist:
meta = MumbleServer( dbus=dbusName );
finally:
meta.secret = icesecret;
meta.save();
for id in servIDs: for id in servIDs:
if v > 1: if v > 1:
print "Checking Murmur instance with id %d." % id; print "Checking Murmur instance with id %d." % id;
# first check that the server has not yet been inserted into the DB # first check that the server has not yet been inserted into the DB
try: try:
instance = models.Mumble.objects.get( dbus=dbusName, srvid=id ); instance = Mumble.objects.get( server=meta, srvid=id );
except models.Mumble.DoesNotExist: except Mumble.DoesNotExist:
values = { values = {
"server": meta,
"srvid": id, "srvid": id,
"dbus": dbusName,
} }
if v > 1: if v:
print "Found new Murmur instance %d on bus '%s'... " % ( id, dbusName ), print "Found new Murmur instance %d on bus '%s'... " % ( id, dbusName )
# now create a model for the record set. # now create a model for the record set.
instance = models.Mumble( **values ); instance = Mumble( **values );
else: else:
if v > 1: if v:
print "Syncing Murmur instance... ", print "Syncing Murmur instance %d: '%s'... " % ( instance.id, instance.name )
try:
instance.configureFromMurmur(); instance.configureFromMurmur();
except DatabaseError, err:
if v > 1: try:
print instance.name; # Find instances with the same address/port
dup = Mumble.objects.get( addr=instance.addr, port=instance.port )
instance.save( dontConfigureMurmur=True ); except Mumble.DoesNotExist:
# None exist - this must've been something else.
print "Server ID / Name: %d / %s" % ( instance.srvid, instance.name )
raise err
else:
print "ERROR: There is already another server instance registered"
print " on the same address and port."
print " -------------"
print " New Server ID:", instance.srvid,
print " New Server Name:", instance.name
print " Address:", instance.addr
print " Port:", instance.port
print " Connection string:", instance.server.dbus
print " -------------"
print " Duplicate Server ID:", dup.srvid,
print "Duplicate Server Name:", dup.name
print " Address:", dup.addr
print " Port:", dup.port
print " Connection string:", dup.server.dbus
return False
except Exception, err:
print "Server ID / Name: %d / %s" % ( instance.srvid, instance.name )
raise err
# Now search for players on this server that have not yet been registered # Now search for players on this server that have not yet been registered
if instance.booted: if instance.booted:
if v > 1: if v > 1:
print "Looking for registered Players on Server id %d." % id; print "Looking for registered Players on Server id %d." % id;
instance.readUsersFromMurmur( verbose=v ); instance.readUsersFromMurmur( verbose=v );
elif v > 1: elif v:
print "This server is not running, can't sync players."; print "This server is not running, can't sync players.";
if v > 1: if v > 1:

View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
"""
* Copyright © 2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
*
* Mumble-Django is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
"""
import os
from os.path import join
from django.db import connection, transaction
from django.db.models import signals
from django.conf import settings
from mumble import models
from mumble.management.server_detect import find_existing_instances
def update_schema( **kwargs ):
if "verbosity" in kwargs:
v = kwargs['verbosity'];
else:
v = 1;
if v:
print "Migrating Database schema for Mumble-Django 2.0 now."
scriptdir = join( settings.CONVERSIONSQL_ROOT, {
'postgresql_psycopg2': 'pgsql',
'postgresql': 'pgsql',
'mysql': 'mysql',
'sqlite3': 'sqlite',
}[settings.DATABASE_ENGINE] )
if v > 1:
print "Reading migration scripts for %s from '%s'" % ( settings.DATABASE_ENGINE, scriptdir )
scripts = [ filename for filename in os.listdir( scriptdir ) if filename.endswith( ".sql" ) ]
scripts.sort()
for filename in scripts:
cursor = connection.cursor()
scriptpath = os.path.join( scriptdir, filename )
scriptfile = open( scriptpath, "r" )
try:
if v > 1:
print "Running migration script '%s'..." % scriptpath
stmt = scriptfile.read()
cursor.execute( stmt )
except IOError, err:
print "Error reading file '%s':" % filename
print err
except cursor.db.connection.Error, err:
print "Error executing file '%s':" % filename
print err
finally:
scriptfile.close()
cursor.close()
if v:
print "Database migration finished successfully."
find_existing_instances( **kwargs )

View File

@@ -22,84 +22,17 @@ class MumbleCtlBase (object):
cache = {}; cache = {};
def getAllConf(self, srvid):
raise NotImplementedError( "mctl::getAllConf" );
def getVersion( self ):
raise NotImplementedError( "mctl::getVersion" );
def getConf(self, srvid, key):
raise NotImplementedError( "mctl::getConf" );
def setConf(self, srvid, key, value):
raise NotImplementedError( "mctl::setConf" );
def getDefaultConf(self):
raise NotImplementedError( "mctl::getDefaultConf" );
def newServer(self):
raise NotImplementedError( "mctl::newServer" );
def setSuperUserPassword(self, srvid, value):
raise NotImplementedError( "mctl::setSuperUserPassword" );
def start(self, srvid):
raise NotImplementedError( "mctl::start" );
def stop(self, srvid):
raise NotImplementedError( "mctl::stop" );
def isBooted(self, srvid):
raise NotImplementedError( "mctl::isBooted" );
def deleteServer(self, srvid):
raise NotImplementedError( "mctl::deleteServer" );
def getPlayers(self, srvid):
raise NotImplementedError( "mctl::getPlayers" );
def getRegisteredPlayers(self, srvid, filter):
raise NotImplementedError( "mctl::getRegisteredPlayers" );
def getChannels(self, srvid):
raise NotImplementedError( "mctl::getChannels" );
def registerPlayer(self, srvid, name, email, password):
raise NotImplementedError( "mctl::registerPlayer" );
def getRegistration(self, srvid, mumbleid):
raise NotImplementedError( "mctl::getRegistration" );
def setRegistration(self, srvid, mumbleid, name, email, password):
raise NotImplementedError( "mctl::setRegistration" );
def unregisterPlayer(self, srvid, mumbleid):
raise NotImplementedError( "mctl::unregisterPlayer" );
def getBootedServers(self):
raise NotImplementedError( "mctl::getBootedServers" );
def getAllServers(self):
raise NotImplementedError( "mctl::getAllServers" );
def getACL(self, srvid, channelid):
raise NotImplementedError( "mctl::getACL" );
def setACL(self, srvid, channelid, acl, groups, inherit):
raise NotImplementedError( "mctl::setACL" );
def getTexture(self, srvid, mumbleid):
raise NotImplementedError( "mctl::getTexture" );
def setTexture(self, srvid, mumbleid, infile):
raise NotImplementedError( "mctl::setTexture" );
def verifyPassword( self, srvid, username, password ):
raise NotImplementedError( "mctl::verifyPassword" );
@staticmethod @staticmethod
def newInstance( connstring, slicefile ): def newInstance( connstring, slicefile=None, icesecret=None ):
""" Create a new CTL object for the given connstring. """ """ Create a new CTL object for the given connstring.
Optional parameters are the path to the slice file and the
Ice secret necessary to authenticate to Murmur.
The path can be omitted only if using DBus or running Murmur
1.2.3 or later, which exports a getSlice method to retrieve
the Slice from.
"""
# check cache # check cache
if connstring in MumbleCtlBase.cache: if connstring in MumbleCtlBase.cache:
@@ -115,7 +48,7 @@ class MumbleCtlBase (object):
ctl = MumbleCtlDbus( connstring ) ctl = MumbleCtlDbus( connstring )
else: else:
from MumbleCtlIce import MumbleCtlIce from MumbleCtlIce import MumbleCtlIce
ctl = MumbleCtlIce( connstring, slicefile ) ctl = MumbleCtlIce( connstring, slicefile, icesecret )
MumbleCtlBase.cache[connstring] = ctl; MumbleCtlBase.cache[connstring] = ctl;
return ctl; return ctl;
@@ -123,6 +56,3 @@ class MumbleCtlBase (object):
@staticmethod @staticmethod
def clearCache(): def clearCache():
MumbleCtlBase.cache = {}; MumbleCtlBase.cache = {};

View File

@@ -14,13 +14,23 @@
* GNU General Public License for more details. * GNU General Public License for more details.
""" """
import socket
import datetime import datetime
import re
from time import time from time import time
from os.path import join
from django.utils.http import urlquote from django.utils.http import urlquote
from django.conf import settings from django.conf import settings
def cmp_channels( left, rite ):
""" Compare two channels, first by position, and if that equals, by name. """
if hasattr( left, "position" ) and hasattr( rite, "position" ):
byorder = cmp( left.position, rite.position );
if byorder != 0:
return byorder;
return cmp_names( left, rite );
def cmp_names( left, rite ): def cmp_names( left, rite ):
""" Compare two objects by their name property. """ """ Compare two objects by their name property. """
return cmp( left.name, rite.name ); return cmp( left.name, rite.name );
@@ -65,7 +75,7 @@ class mmChannel( object ):
return self._acl; return self._acl;
acl = property( getACL, doc=getACL.__doc__ ); acl = property( getACL );
is_server = False; is_server = False;
@@ -95,7 +105,7 @@ class mmChannel( object ):
def sort( self ): def sort( self ):
""" Sort my subchannels and players, and then iterate over them and sort them recursively. """ """ Sort my subchannels and players, and then iterate over them and sort them recursively. """
self.subchans.sort( cmp_names ); self.subchans.sort( cmp_channels );
self.players.sort( cmp_names ); self.players.sort( cmp_names );
for subc in self.subchans: for subc in self.subchans:
subc.sort(); subc.sort();
@@ -113,26 +123,23 @@ class mmChannel( object ):
""" Create an URL to connect to this channel. The URL is of the form """ Create an URL to connect to this channel. The URL is of the form
mumble://username@host:port/parentchans/self.name mumble://username@host:port/parentchans/self.name
""" """
userstr = ""; from urlparse import urlunsplit
versionstr = "version=%d.%d.%d" % tuple(self.server.version[:3]);
if self.parent is not None:
chanlist = self.parent_channels() + [self.name];
chanlist = [ urlquote( chan ) for chan in chanlist ];
urlpath = "/".join( chanlist );
else:
urlpath = "";
if for_user is not None: if for_user is not None:
userstr = "%s@" % for_user.name; netloc = "%s@%s" % ( for_user.name, self.server.netloc );
return urlunsplit(( "mumble", netloc, urlpath, versionstr, "" ))
else:
return urlunsplit(( "mumble", self.server.netloc, urlpath, versionstr, "" ))
versionstr = "version=%d.%d.%d" % tuple(self.server.version[0:3]); connecturl = property( getURL );
# create list of all my parents and myself
chanlist = self.parent_channels() + [self.name];
# urlencode channel names
chanlist = [ urlquote( chan ) for chan in chanlist ];
# create a path by joining the channel names
chanpath = join( *chanlist );
if self.server.port != settings.MUMBLE_DEFAULT_PORT:
return "mumble://%s%s:%d/%s?%s" % ( userstr, self.server.addr, self.server.port, chanpath, versionstr );
return "mumble://%s%s/%s?%s" % ( userstr, self.server.addr, chanpath, versionstr );
connecturl = property( getURL, doc="A convenience wrapper for getURL." );
def setDefault( self ): def setDefault( self ):
""" Make this the server's default channel. """ """ Make this the server's default channel. """
@@ -150,6 +157,32 @@ class mmChannel( object ):
chandata['subchans'] = [ sc.asDict() for sc in self.subchans ]; chandata['subchans'] = [ sc.asDict() for sc in self.subchans ];
return chandata; return chandata;
def asMvXml( self, parentnode ):
""" Return an XML tree for this channel suitable for MumbleViewer-ng. """
from xml.etree.cElementTree import SubElement
me = SubElement( parentnode, "item" , id=self.id, rel='channel' )
content = SubElement( me, "content" )
name = SubElement( content , "name" )
name.text = self.name
for sc in self.subchans:
sc.asMvXml(me)
for pl in self.players:
pl.asMvXml(me)
def asMvJson( self ):
""" Return a Dict for this channel suitable for MumbleViewer-ng. """
return {
"attributes": {
"href": self.connecturl,
"id": self.id,
"rel": "channel",
},
"data": self.name,
"children": [ sc.asMvJson() for sc in self.subchans ] + \
[ pl.asMvJson() for pl in self.players ],
"state": { False: "closed", True: "open" }[self.top_or_not_empty],
}
@@ -201,6 +234,21 @@ class mmPlayer( object ):
is_channel = False; is_channel = False;
is_player = True; is_player = True;
def getIpAsString( self ):
""" Get the client's IPv4 or IPv6 address, in a pretty format. """
addr = self.player_obj.address;
if max( addr[:10] ) == 0 and addr[10:12] == (255, 255):
return "%d.%d.%d.%d" % tuple( addr[12:] );
ip6addr = [(hi << 8 | lo) for (hi, lo) in zip(addr[0::2], addr[1::2])]
# colon-separated string:
ipstr = ':'.join([ ("%x" % part) for part in ip6addr ]);
# 0:0:0 -> ::
return re.sub( "((^|:)(0:){2,})", '::', ipstr, 1 );
ipaddress = property( getIpAsString );
fqdn = property( lambda self: socket.getfqdn( self.ipaddress ),
doc="The fully qualified domain name of the user's host." );
# kept for compatibility to mmChannel (useful for traversal funcs) # kept for compatibility to mmChannel (useful for traversal funcs)
playerCount = property( lambda self: -1, doc="Exists only for compatibility to mmChannel." ); playerCount = property( lambda self: -1, doc="Exists only for compatibility to mmChannel." );
@@ -222,6 +270,24 @@ class mmPlayer( object ):
return pldata; return pldata;
def asMvXml( self, parentnode ):
""" Return an XML node for this player suitable for MumbleViewer-ng. """
from xml.etree.cElementTree import SubElement
me = SubElement( parentnode, "item" , id=self.id, rel='user' )
content = SubElement( me, "content" )
name = SubElement( content , "name" )
name.text = self.name
def asMvJson( self ):
""" Return a Dict for this player suitable for MumbleViewer-ng. """
return {
"attributes": {
"id": self.id,
"rel": "user",
},
'data': self.name,
}
class mmACL( object ): class mmACL( object ):

View File

@@ -14,9 +14,10 @@
* GNU General Public License for more details. * GNU General Public License for more details.
""" """
import socket import socket, Ice, re
from sys import stderr
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_noop, ugettext_lazy as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.db.models import signals from django.db.models import signals
@@ -26,20 +27,119 @@ from mumble.mmobjects import mmChannel, mmPlayer
from mumble.mctl import MumbleCtlBase from mumble.mctl import MumbleCtlBase
def mk_config_property( field, doc="" ): def mk_config_property( field, doc="", get_coerce=None, get_none=None, set_coerce=unicode, set_none='' ):
""" Create a property for the given config field. """ """ Create a property for the given config field. """
def get_field( self ): def get_field( self ):
if self.id is not None: if self.id is not None:
return self.getConf( field ) val = self.getConf( field );
else: if val is None or val == '':
return get_none
if callable(get_coerce):
return get_coerce( val )
return val
return None return None
def set_field( self, value ): def set_field( self, value ):
if value is None:
self.setConf( field, set_none )
elif callable(set_coerce):
self.setConf( field, set_coerce(value) )
else:
self.setConf( field, value ) self.setConf( field, value )
return property( get_field, set_field, doc=doc ) return property( get_field, set_field, doc=doc )
def mk_config_bool_property( field, doc="" ):
return mk_config_property( field, doc=doc,
get_coerce = lambda value: value == "true",
set_coerce = lambda value: str(value).lower()
);
class MumbleServer( models.Model ):
""" Represents a Murmur server installation. """
dbus = models.CharField( _('DBus or ICE base'), max_length=200, unique=True, default=settings.DEFAULT_CONN, help_text=_(
"Examples: 'net.sourceforge.mumble.murmur' for DBus or 'Meta:tcp -h 127.0.0.1 -p 6502' for Ice.") );
secret = models.CharField( _('Ice Secret'), max_length=200, blank=True );
class Meta:
verbose_name = _('Mumble Server');
verbose_name_plural = _('Mumble Servers');
def __init__( self, *args, **kwargs ):
models.Model.__init__( self, *args, **kwargs );
self._ctl = None;
self._conf = None;
def __unicode__( self ):
return self.dbus;
# Ctl instantiation
def getCtl( self ):
""" Instantiate and return a MumbleCtl object for this server.
Only one instance will be created, and reused on subsequent calls.
"""
if not self._ctl:
self._ctl = MumbleCtlBase.newInstance( self.dbus, settings.SLICE, self.secret );
return self._ctl;
ctl = property( getCtl, doc="Get a Control object for this server. The ctl is cached for later reuse." );
def isMethodDbus(self):
""" Return true if this instance uses DBus. """
rd = re.compile( r'^(\w+\.)*\w+$' );
return bool(rd.match(self.dbus))
method_dbus = property( isMethodDbus )
method_ice = property( lambda self: not self.isMethodDbus(), doc="Return true if this instance uses Ice." )
def getDefaultConf( self, field=None ):
""" Get a field from the default conf dictionary, or None if the field isn't set. """
if self._conf is None:
self._conf = self.ctl.getDefaultConf()
if field is None:
return self._conf
if field in self._conf:
return self._conf[field]
return None
def isOnline( self ):
""" Return true if this server process is running. """
possibleexceptions = []
try:
from Ice import ConnectionRefusedException
except ImportError, err:
if self.method_ice:
print >> stderr, err
return None
else:
possibleexceptions.append( ConnectionRefusedException )
try:
from dbus import DBusException
except ImportError, err:
if self.method_dbus:
print >> stderr, err
return None
else:
possibleexceptions.append( DBusException )
try:
self.ctl
except tuple(possibleexceptions), err:
print >> stderr, err
return False
except (EnvironmentError, RuntimeError), err:
print >> stderr, err
return None
else:
return True
online = property( isOnline )
defaultconf = property( getDefaultConf, doc="The default config dictionary." )
class Mumble( models.Model ): class Mumble( models.Model ):
""" Represents a Murmur server instance. """ Represents a Murmur server instance.
@@ -56,42 +156,49 @@ class Mumble( models.Model ):
deleted as well. deleted as well.
""" """
server = models.ForeignKey( MumbleServer, verbose_name=_("Mumble Server") );
name = models.CharField( _('Server Name'), max_length=200 ); name = models.CharField( _('Server Name'), max_length=200 );
dbus = models.CharField( _('DBus or ICE base'), max_length = 200, default = settings.DEFAULT_CONN, help_text=_(
"Examples: 'net.sourceforge.mumble.murmur' for DBus or 'Meta:tcp -h 127.0.0.1 -p 6502' for Ice.") );
srvid = models.IntegerField( _('Server ID'), editable=False ); srvid = models.IntegerField( _('Server ID'), editable=False );
addr = models.CharField( _('Server Address'), max_length = 200, help_text=_( addr = models.CharField( _('Server Address'), max_length=200, blank=True, help_text=_(
"Hostname or IP address to bind to. You should use a hostname here, because it will appear on the " "Hostname or IP address to bind to. You should use a hostname here, because it will appear on the "
"global server list.") ); "global server list.") );
port = models.IntegerField( _('Server Port'), default=settings.MUMBLE_DEFAULT_PORT, help_text=_( port = models.IntegerField( _('Server Port'), blank=True, null=True, help_text=_(
"Port number to bind to. Use -1 to auto assign one.") ); "Port number to bind to. Leave empty to auto assign one.") );
display = models.CharField( _('Server Display Address'), max_length=200, blank=True, help_text=_(
"This field is only relevant if you are located behind a NAT, and names the Hostname or IP address "
"to use in the Channel Viewer and for the global server list registration. If not given, the addr "
"and port fields are used. If display and bind ports are equal, you can omit it here.") );
supw = property( lambda self: '', supw = property( lambda self: '',
lambda self, value: self.ctl.setSuperUserPassword( self.srvid, value ), lambda self, value: ( value and self.ctl.setSuperUserPassword( self.srvid, value ) ) or None,
doc='Superuser Password' doc=_('Superuser Password')
) )
url = mk_config_property( "registerurl", "Website URL" ) url = mk_config_property( "registerurl", ugettext_noop("Website URL") )
motd = mk_config_property( "welcometext", "Welcome Message" ) motd = mk_config_property( "welcometext", ugettext_noop("Welcome Message") )
passwd = mk_config_property( "password", "Server Password" ) passwd = mk_config_property( "password", ugettext_noop("Server Password") )
users = mk_config_property( "users", "Max. Users" ) users = mk_config_property( "users", ugettext_noop("Max. Users"), get_coerce=int )
bwidth = mk_config_property( "bandwidth", "Bandwidth [Bps]" ) bwidth = mk_config_property( "bandwidth", ugettext_noop("Bandwidth [Bps]"), get_coerce=int )
sslcrt = mk_config_property( "certificate", "SSL Certificate" ) sslcrt = mk_config_property( "certificate", ugettext_noop("SSL Certificate") )
sslkey = mk_config_property( "key", "SSL Key" ) sslkey = mk_config_property( "key", ugettext_noop("SSL Key") )
player = mk_config_property( "username", "Player name regex" ) player = mk_config_property( "username", ugettext_noop("Player name regex") )
channel = mk_config_property( "channelname", "Channel name regex" ) channel = mk_config_property( "channelname", ugettext_noop("Channel name regex") )
defchan = mk_config_property( "defaultchannel", "Default channel" ) defchan = mk_config_property( "defaultchannel", ugettext_noop("Default channel"), get_coerce=int )
timeout = mk_config_property( "timeout", ugettext_noop("Timeout"), get_coerce=int )
obfsc = property( obfsc = mk_config_bool_property( "obfuscate", ugettext_noop("IP Obfuscation") )
lambda self: ( self.getConf( "obfuscate" ) == "true" ) if self.id is not None else None, certreq = mk_config_bool_property( "certrequired", ugettext_noop("Require Certificate") )
lambda self, value: self.setConf( "obfuscate", str(value).lower() ), textlen = mk_config_bool_property( "textmessagelength", ugettext_noop("Maximum length of text messages") )
doc="IP Obfuscation" html = mk_config_bool_property( "allowhtml", ugettext_noop("Allow HTML to be used in messages") )
) bonjour = mk_config_bool_property( "bonjour", ugettext_noop("Publish this server via Bonjour") )
autoboot= mk_config_bool_property( "boot", ugettext_noop("Boot Server when Murmur starts") )
def getBooted( self ): def getBooted( self ):
if self.id is not None: if self.id is not None:
return self.ctl.isBooted( self.srvid ); if self.server.online:
return self.ctl.isBooted( self.srvid )
else:
return None
else: else:
return False return False
@@ -102,10 +209,11 @@ class Mumble( models.Model ):
else: else:
self.ctl.stop( self.srvid ); self.ctl.stop( self.srvid );
booted = property( getBooted, setBooted, doc="Boot Server" ) booted = property( getBooted, setBooted, doc=ugettext_noop("Boot Server") )
online = property( getBooted, setBooted, doc=ugettext_noop("Boot Server") )
class Meta: class Meta:
unique_together = ( ( 'dbus', 'srvid' ), ( 'addr', 'port' ), ); unique_together = ( ( 'server', 'srvid' ), );
verbose_name = _('Server instance'); verbose_name = _('Server instance');
verbose_name_plural = _('Server instances'); verbose_name_plural = _('Server instances');
@@ -119,52 +227,33 @@ class Mumble( models.Model ):
but to Murmur as well. but to Murmur as well.
""" """
if dontConfigureMurmur: if dontConfigureMurmur:
# skip murmur configuration, e.g. because we're inserting models for existing servers.
return models.Model.save( self ); return models.Model.save( self );
# check if this server already exists, if not call newServer and set my srvid first
if self.id is None: if self.id is None:
self.srvid = self.ctl.newServer(); self.srvid = self.ctl.newServer();
if self.port == -1:
self.port = max( [ rec['port'] for rec in Mumble.objects.values('port') ] ) + 1;
if self.port < 1 or self.port >= 2**16:
raise ValueError( _("Port number %(portno)d is not within the allowed range %(minrange)d - %(maxrange)d") % {
'portno': self.port,
'minrange': 1,
'maxrange': 2**16,
});
self.ctl.setConf( self.srvid, 'host', socket.gethostbyname( self.addr ) );
self.ctl.setConf( self.srvid, 'port', str(self.port) );
self.ctl.setConf( self.srvid, 'registername', self.name ); self.ctl.setConf( self.srvid, 'registername', self.name );
self.ctl.setConf( self.srvid, 'registerurl', self.url );
# registerHostname needs to take the port no into account if self.addr and self.addr != '0.0.0.0':
if self.port and self.port != settings.MUMBLE_DEFAULT_PORT: self.ctl.setConf( self.srvid, 'host', socket.gethostbyname(self.addr) );
self.ctl.setConf( self.srvid, 'registerhostname', "%s:%d" % ( self.addr, self.port ) );
else: else:
self.ctl.setConf( self.srvid, 'registerhostname', self.addr ); self.ctl.setConf( self.srvid, 'host', '' );
if self.supw: if self.port and self.port != settings.MUMBLE_DEFAULT_PORT + self.srvid - 1:
self.ctl.setSuperUserPassword( self.srvid, self.supw ); self.ctl.setConf( self.srvid, 'port', str(self.port) );
self.supw = '';
if self.booted != self.ctl.isBooted( self.srvid ):
if self.booted:
self.ctl.start( self.srvid );
else: else:
self.ctl.stop( self.srvid ); self.ctl.setConf( self.srvid, 'port', '' );
if self.netloc:
self.ctl.setConf( self.srvid, 'registerhostname', self.netloc );
else:
self.ctl.setConf( self.srvid, 'registerhostname', '' );
# Now allow django to save the record set
return models.Model.save( self ); return models.Model.save( self );
def __init__( self, *args, **kwargs ): def __init__( self, *args, **kwargs ):
models.Model.__init__( self, *args, **kwargs ); models.Model.__init__( self, *args, **kwargs );
self._ctl = None;
self._channels = None; self._channels = None;
self._rootchan = None; self._rootchan = None;
@@ -172,26 +261,14 @@ class Mumble( models.Model ):
users_regged = property( lambda self: self.mumbleuser_set.count(), doc="Number of registered users." ); users_regged = property( lambda self: self.mumbleuser_set.count(), doc="Number of registered users." );
users_online = property( lambda self: len(self.ctl.getPlayers(self.srvid)), doc="Number of online users." ); users_online = property( lambda self: len(self.ctl.getPlayers(self.srvid)), doc="Number of online users." );
channel_cnt = property( lambda self: len(self.ctl.getChannels(self.srvid)), doc="Number of channels." ); channel_cnt = property( lambda self: len(self.ctl.getChannels(self.srvid)), doc="Number of channels." );
is_public = property( lambda self: self.passwd == '', is_public = property( lambda self: not self.passwd,
doc="False if a password is needed to join this server." ); doc="False if a password is needed to join this server." );
is_server = True; is_server = True;
is_channel = False; is_channel = False;
is_player = False; is_player = False;
ctl = property( lambda self: self.server.ctl );
# Ctl instantiation
def getCtl( self ):
""" Instantiate and return a MumbleCtl object for this server.
Only one instance will be created, and reused on subsequent calls.
"""
if not self._ctl:
self._ctl = MumbleCtlBase.newInstance( self.dbus, settings.SLICE );
return self._ctl;
ctl = property( getCtl, doc="Get a Control object for this server. The ctl is cached for later reuse." );
def getConf( self, field ): def getConf( self, field ):
return self.ctl.getConf( self.srvid, field ) return self.ctl.getConf( self.srvid, field )
@@ -200,38 +277,54 @@ class Mumble( models.Model ):
return self.ctl.setConf( self.srvid, field, value ) return self.ctl.setConf( self.srvid, field, value )
def configureFromMurmur( self ): def configureFromMurmur( self ):
default = self.ctl.getDefaultConf();
conf = self.ctl.getAllConf( self.srvid ); conf = self.ctl.getAllConf( self.srvid );
def find_in_dicts( keys, valueIfNotFound=None ): if "registername" not in conf or not conf["registername"]:
if not isinstance( keys, tuple ): self.name = "noname";
keys = ( keys, ); else:
self.name = conf["registername"];
for keyword in keys: if "registerhostname" in conf and conf["registerhostname"]:
if keyword in conf: if ':' in conf["registerhostname"]:
return conf[keyword]; regname, regport = conf["registerhostname"].split(':')
regport = int(regport)
else:
regname = conf["registerhostname"]
regport = None
else:
regname = None
regport = None
for keyword in keys: if "host" in conf and conf["host"]:
keyword = keyword.lower(); addr = conf["host"]
if keyword in default: else:
return default[keyword]; addr = None
return valueIfNotFound; if "port" in conf and conf["port"]:
self.port = int(conf["port"])
else:
self.port = None
servername = find_in_dicts( "registername", "noname" ); if regname and addr:
if not servername: if regport == self.port:
# RegistrationName was found in the dicts, but is an empty string if socket.gethostbyname(regname) == socket.gethostbyname(addr):
servername = "noname"; self.display = ''
self.addr = regname
addr = find_in_dicts( ( "registerhostname", "host" ), "0.0.0.0" ); else:
if addr.find( ':' ) != -1: self.display = regname
# The addr is a hostname which actually contains a port number, but we already got that from self.addr = addr
# the port field, so we can simply drop it. else:
addr = addr.split(':')[0]; self.display = conf["registerhostname"]
self.addr = addr
self.name = servername; elif regname and not addr:
self.addr = addr; self.display = regname
self.port = find_in_dicts( "port" ); self.addr = ''
elif addr and not regname:
self.display = ''
self.addr = addr
else:
self.display = ''
self.addr = ''
self.save( dontConfigureMurmur=True ); self.save( dontConfigureMurmur=True );
@@ -241,6 +334,9 @@ class Mumble( models.Model ):
raise SystemError( "This murmur instance is not currently running, can't sync." ); raise SystemError( "This murmur instance is not currently running, can't sync." );
players = self.ctl.getRegisteredPlayers(self.srvid); players = self.ctl.getRegisteredPlayers(self.srvid);
known_ids = [rec["mumbleid"]
for rec in MumbleUser.objects.filter( server=self ).values( "mumbleid" )
]
for idx in players: for idx in players:
playerdata = players[idx]; playerdata = players[idx];
@@ -248,12 +344,9 @@ class Mumble( models.Model ):
if playerdata.userid == 0: # Skip SuperUsers if playerdata.userid == 0: # Skip SuperUsers
continue; continue;
if verbose > 1: if verbose > 1:
print "Checking Player with id %d and name '%s'." % ( playerdata.userid, playerdata.name ); print "Checking Player with id %d." % playerdata.userid;
try: if playerdata.userid not in known_ids:
playerinstance = MumbleUser.objects.get( server=self, mumbleid=playerdata.userid );
except MumbleUser.DoesNotExist:
if verbose: if verbose:
print 'Found new Player "%s".' % playerdata.name; print 'Found new Player "%s".' % playerdata.name;
@@ -267,8 +360,8 @@ class Mumble( models.Model ):
else: else:
if verbose > 1: if verbose > 1:
print "This player is already listed in the database."; print "Player '%s' is already known." % playerdata.name;
playerinstance = MumbleUser.objects.get( server=self, mumbleid=playerdata.userid );
playerinstance.name = playerdata.name; playerinstance.name = playerdata.name;
playerinstance.save( dontConfigureMurmur=True ); playerinstance.save( dontConfigureMurmur=True );
@@ -277,6 +370,8 @@ class Mumble( models.Model ):
def isUserAdmin( self, user ): def isUserAdmin( self, user ):
""" Determine if the given user is an admin on this server. """ """ Determine if the given user is an admin on this server. """
if user.is_authenticated(): if user.is_authenticated():
if user.is_superuser:
return True;
try: try:
return self.mumbleuser_set.get( owner=user ).getAdmin(); return self.mumbleuser_set.get( owner=user ).getAdmin();
except MumbleUser.DoesNotExist: except MumbleUser.DoesNotExist:
@@ -355,20 +450,39 @@ class Mumble( models.Model ):
channels = property( getChannels, doc="A convenience wrapper for getChannels()." ); channels = property( getChannels, doc="A convenience wrapper for getChannels()." );
rootchan = property( lambda self: self.channels[0], doc="A convenience wrapper for getChannels()[0]." ); rootchan = property( lambda self: self.channels[0], doc="A convenience wrapper for getChannels()[0]." );
def getNetloc( self ):
""" Return the address from the Display field (if any), or the server address.
Users from outside a NAT will need to use the Display address to connect
to this server instance.
"""
if self.display:
if ":" in self.display:
return self.display;
else:
daddr = self.display;
else:
daddr = self.addr;
if self.port and self.port != settings.MUMBLE_DEFAULT_PORT:
return "%s:%d" % (daddr, self.port);
else:
return daddr;
netloc = property( getNetloc );
def getURL( self, forUser = None ): def getURL( self, forUser = None ):
""" Create an URL of the form mumble://username@host:port/ for this server. """ """ Create an URL of the form mumble://username@host:port/ for this server. """
userstr = ""; if not self.netloc:
return None
from urlparse import urlunsplit
versionstr = "version=%d.%d.%d" % tuple(self.version[:3]);
if forUser is not None: if forUser is not None:
userstr = "%s@" % forUser.name; netloc = "%s@%s" % ( forUser.name, self.netloc );
return urlunsplit(( "mumble", netloc, "", versionstr, "" ))
else:
return urlunsplit(( "mumble", self.netloc, "", versionstr, "" ))
versionstr = "version=%d.%d.%d" % tuple(self.version[0:3]); connecturl = property( getURL );
if self.port != settings.MUMBLE_DEFAULT_PORT:
return "mumble://%s%s:%d/?%s" % ( userstr, self.addr, self.port, versionstr );
return "mumble://%s%s/?%s" % ( userstr, self.addr, versionstr );
connecturl = property( getURL, doc="A convenience wrapper for getURL()." );
version = property( lambda self: self.ctl.getVersion(), doc="The version of Murmur." ); version = property( lambda self: self.ctl.getVersion(), doc="The version of Murmur." );
@@ -378,6 +492,43 @@ class Mumble( models.Model ):
'root': self.rootchan.asDict() 'root': self.rootchan.asDict()
}; };
def asMvXml( self ):
""" Return an XML tree for this server suitable for MumbleViewer-ng. """
from xml.etree.cElementTree import Element
root = Element("root")
self.rootchan.asMvXml(root)
return root
def asMvJson( self ):
""" Return a Dict for this server suitable for MumbleViewer-ng. """
return self.rootchan.asMvJson()
# "server" field protection
def __setattr__( self, name, value ):
if name == 'server':
if self.id is not None and self.server != value:
raise AttributeError( _( "This field must not be updated once the record has been saved." ) );
models.Model.__setattr__( self, name, value );
def kickUser( self, sessionid, reason="" ):
return self.ctl.kickUser( self.srvid, sessionid, reason );
def banUser( self, sessionid, reason="" ):
return self.ctl.addBanForSession( self.srvid, sessionid, reason=reason );
def mk_registration_property( field, doc="" ):
""" Create a property for the given registration field. """
def get_field( self ):
if "comment" in self.registration:
return self.registration["comment"];
else:
return None;
return property( get_field, doc=doc )
class MumbleUser( models.Model ): class MumbleUser( models.Model ):
@@ -399,6 +550,9 @@ class MumbleUser( models.Model ):
server = models.ForeignKey( Mumble, verbose_name=_('Server instance'), related_name="mumbleuser_set" ); server = models.ForeignKey( Mumble, verbose_name=_('Server instance'), related_name="mumbleuser_set" );
owner = models.ForeignKey( User, verbose_name=_('Account owner'), related_name="mumbleuser_set", null=True, blank=True ); owner = models.ForeignKey( User, verbose_name=_('Account owner'), related_name="mumbleuser_set", null=True, blank=True );
comment = mk_registration_property( "comment", doc=ugettext_noop("The user's comment.") );
hash = mk_registration_property( "hash", doc=ugettext_noop("The user's hash.") );
class Meta: class Meta:
unique_together = ( ( 'server', 'owner' ), ( 'server', 'mumbleid' ) ); unique_together = ( ( 'server', 'owner' ), ( 'server', 'mumbleid' ) );
verbose_name = _( 'User account' ); verbose_name = _( 'User account' );
@@ -418,10 +572,8 @@ class MumbleUser( models.Model ):
def save( self, dontConfigureMurmur=False ): def save( self, dontConfigureMurmur=False ):
""" Save the settings in this model to Murmur. """ """ Save the settings in this model to Murmur. """
if dontConfigureMurmur: if dontConfigureMurmur:
# skip murmur configuration, e.g. because we're inserting models for existing players.
return models.Model.save( self ); return models.Model.save( self );
# Before the record set is saved, update Murmur via controller.
ctl = self.server.ctl; ctl = self.server.ctl;
if self.owner: if self.owner:
@@ -451,7 +603,6 @@ class MumbleUser( models.Model ):
# Don't save the users' passwords, we don't need them anyway # Don't save the users' passwords, we don't need them anyway
self.password = ''; self.password = '';
# Now allow django to save the record set
return models.Model.save( self ); return models.Model.save( self );
def __init__( self, *args, **kwargs ): def __init__( self, *args, **kwargs ):
@@ -461,10 +612,15 @@ class MumbleUser( models.Model ):
# Admin handlers # Admin handlers
def getAdmin( self ): def getAdmin( self ):
""" Get ACL of root Channel, get the admin group and see if this user is in it. """ """ Get ACL of root Channel, get the admin group and see if this user is in it. """
if self.mumbleid == -1:
return False;
else:
return self.server.rootchan.acl.group_has_member( "admin", self.mumbleid ); return self.server.rootchan.acl.group_has_member( "admin", self.mumbleid );
def setAdmin( self, value ): def setAdmin( self, value ):
""" Set or revoke this user's membership in the admin group on the root channel. """ """ Set or revoke this user's membership in the admin group on the root channel. """
if self.mumbleid == -1:
return False;
if value: if value:
self.server.rootchan.acl.group_add_member( "admin", self.mumbleid ); self.server.rootchan.acl.group_add_member( "admin", self.mumbleid );
else: else:
@@ -472,7 +628,8 @@ class MumbleUser( models.Model ):
self.server.rootchan.acl.save(); self.server.rootchan.acl.save();
return value; return value;
aclAdmin = property( getAdmin, setAdmin, doc="Wrapper around getAdmin/setAdmin (not a database field like isAdmin)" ); aclAdmin = property( getAdmin, setAdmin, doc=ugettext_noop('Admin on root channel') );
# Registration fetching # Registration fetching
def getRegistration( self ): def getRegistration( self ):
@@ -481,38 +638,19 @@ class MumbleUser( models.Model ):
self._registration = self.server.ctl.getRegistration( self.server.srvid, self.mumbleid ); self._registration = self.server.ctl.getRegistration( self.server.srvid, self.mumbleid );
return self._registration; return self._registration;
registration = property( getRegistration, doc=getRegistration.__doc__ ); registration = property( getRegistration );
def getComment( self ):
""" Retrieve a user's comment, if any. """
if "comment" in self.registration:
return self.registration["comment"];
else:
return None;
comment = property( getComment, doc=getComment.__doc__ );
def getHash( self ):
""" Retrieve a user's hash, if any. """
if "hash" in self.registration:
return self.registration["hash"];
else:
return None;
hash = property( getHash, doc=getHash.__doc__ );
# Texture handlers # Texture handlers
def getTexture( self ): def getTexture( self ):
""" Get the user texture as a PIL Image. """ """ Get the user texture as a PIL Image. """
return self.server.ctl.getTexture(self.server.srvid, self.mumbleid); return self.server.ctl.getTexture(self.server.srvid, self.mumbleid);
def setTexture( self, infile ): def setTexture( self, image ):
""" Read an image from the infile and install it as the user's texture. """ """ Install the given image as the user's texture. """
self.server.ctl.setTexture(self.server.srvid, self.mumbleid, infile) self.server.ctl.setTexture(self.server.srvid, self.mumbleid, image)
texture = property( getTexture, setTexture, texture = property( getTexture, setTexture,
doc="Get the texture as a PIL Image or read from a file (pass the path)." doc="Get the texture as a PIL Image or set the Image as the texture."
); );
def hasTexture( self ): def hasTexture( self ):
@@ -530,10 +668,10 @@ class MumbleUser( models.Model ):
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
return reverse( showTexture, kwargs={ 'server': self.server.id, 'userid': self.id } ); return reverse( showTexture, kwargs={ 'server': self.server.id, 'userid': self.id } );
textureUrl = property( getTextureUrl, doc=getTextureUrl.__doc__ ); textureUrl = property( getTextureUrl );
# Deletion handler # Deletion handler
@staticmethod @staticmethod
def pre_delete_listener( **kwargs ): def pre_delete_listener( **kwargs ):
kwargs['instance'].unregister(); kwargs['instance'].unregister();
@@ -546,7 +684,6 @@ class MumbleUser( models.Model ):
# "server" field protection # "server" field protection
def __setattr__( self, name, value ): def __setattr__( self, name, value ):
if name == 'server': if name == 'server':
if self.id is not None and self.server != value: if self.id is not None and self.server != value:

View File

@@ -0,0 +1,28 @@
{% comment %}
<!-- kate: space-indent on; indent-width 2; replace-tabs on; -->
{% endcomment %}
{% load mumble_extras %}
<div class="mumble" style="background-image: url( {{ MEDIA_URL }}/mumble/linie_v.png )">
<img src="{{ MEDIA_URL }}/mumble/knoten_v.png" alt="" />
{% if Channel.linked %}
<img src="{{ MEDIA_URL }}/mumble/channel_linked.png" alt="linked channel" />
{% else %}
<img src="{{ MEDIA_URL }}/mumble/channel.png" alt="channel" />
{% endif %}
{% if Channel.server.netloc %}
<a href="{{ Channel|chanurl:MumbleAccount }}" class="mumble" id="link_{{ Channel.id }}" title="{{ Channel.name }}">
{{ Channel.name|trunc:30 }}
</a>
{% else %}
<a class="mumble" id="link_{{ Channel.id }}" title="{{ Channel.name }}">
{{ Channel.name|trunc:30 }}
</a>
{% endif %}
{% for sub in Channel.subchans %}
{% if sub.show %}
{{ sub|chanview:MumbleAccount }}
{% endif %}
{% endfor %}
{% for player in Channel.players %}{{ player|chanview }}{% endfor %}
</div>

View File

@@ -0,0 +1,24 @@
{% extends "index.html" %}
{% comment %}
<!-- kate: space-indent on; indent-width 2; replace-tabs on; -->
{% endcomment %}
{% load i18n %}
{% load mumble_extras %}
{% block Headline %}
Configured Mumble Servers
{% endblock %}
{% block Content %}
<div class="rahmen">
<ul>
{% for mumble in MumbleObjects %}
{% if mumble.booted %}
<li><a href="{% url mumble.views.show mumble.id %}">{{ mumble.name }}</a></li>
{% else %}
<li>{{ mumble.name }} (offline)</li>
{% endif %}
{% empty %}
{% trans "No server instances have been configured yet." %}
{% endfor %}
</ul>
</div>
{% endblock %}

View File

@@ -0,0 +1,21 @@
{% extends "mobile_index.html" %}
{% comment %}
<!-- kate: space-indent on; indent-width 2; replace-tabs on; -->
{% endcomment %}
{% load mumble_extras %}
{% block Headline %}
Configured Mumble Servers
{% endblock %}
{% block LeftColumn %}
<div class="rahmen">
<ul>
{% for mumble in MumbleObjects %}
{% if mumble.booted %}
<li><a href="{% url mumble.views.mobile_show mumble.id %}">{{ mumble.name }}</a></li>
{% else %}
<li>{{ mumble.name }} (offline)</li>
{% endif %}
{% endfor %}
</ul>
</div>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "mobile_index.html" %}
{% comment %}
<!-- kate: space-indent on; indent-width 2; replace-tabs on; -->
{% endcomment %}
{% load mumble_extras %}
{% load i18n %}
{% block Headline %}
{{ DBaseObject.name }}
{% endblock %}
{% block LeftColumn %}
{{ DBaseObject|chanview:MumbleAccount }}
{% endblock %}

View File

@@ -0,0 +1,388 @@
{% extends "index.html" %}
{% comment %}
<!-- kate: space-indent on; indent-width 2; replace-tabs on; -->
{% endcomment %}
{% load mumble_extras %}
{% load i18n %}
{% block Headline %}
{{ DBaseObject.name }}
{% endblock %}
{% block LeftColumn %}
{{ DBaseObject|chanview:MumbleAccount }}
{% endblock %}
{% block Content %}
<noscript>
<p>
{% blocktrans %}
<b>Hint:</b><br />
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
that are displayed.
{% endblocktrans %}
</p>
</noscript>
<div id="mumble_ext_container"></div>
<div id="mumble_motd" class="mumble-ext x-hide-display">
<ul>
{% if DBaseObject.connecturl %}
<li>{% trans "Server Address" %}: <a href="{{ DBaseObject.connecturl }}">{{ DBaseObject.connecturl }}</a></li>
{% endif %}
{% if DBaseObject.url %}
<li>{% trans "Website" %}: {{ DBaseObject.url|urlize }}</li>
{% endif %}
<li>{% trans "Server version" %}: {{ DBaseObject.version.0 }}.{{ DBaseObject.version.1 }}.{{ DBaseObject.version.2 }}</li>
<li><a href="{% url mumble.views.mobile_show DBaseObject.id %}">{% trans "Minimal view" %}</a></li>
</ul>
<fieldset>
<legend>{% trans "Welcome message" %}</legend>
{{ DBaseObject.motd|removetags:"script link meta html head body style"|safe }}
</fieldset>
</div>
<div id="mumble_registration" class="mumble-ext">
{% if user.is_authenticated %}
<h2>{% trans "Server registration" %}</h2>
<form action="{% url mumble.views.show DBaseObject.id %}" method="post">
{% if Registered %}
{% trans "You are registered on this server" %}.<br />
{% else %}
{% trans "You do not have an account on this server" %}.<br />
{% endif %}
<table>
{{ RegForm }}
</table>
<input type="hidden" name="mode" value="reg" />
<input type="submit" />
</form>
{% else %}
{% 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 %}
</div>
{% if Registered %}
<div id="mumble_texture" class="mumble-ext">
<h2>{% trans "User Texture" %}</h2>
{% if DBaseObject|mmversion_eq:"1.2.2" %}
{% blocktrans %}
Sorry, due to a bug in Murmur 1.2.2, displaying and setting the Texture is disabled.
{% endblocktrans %}
{% else %}
<p>
{% blocktrans with DBaseObject.id as serverid %}
You can upload an image that you would like to use as your user texture here.
{% endblocktrans %}<br />
<br />
{% if MumbleAccount.hasTexture %}
{% trans "Your current texture is" %}:<br />
<img src="{% url mumble.views.showTexture DBaseObject.id MumbleAccount.id %}" alt="user texture" /><br />
{% else %}
{% trans "You don't currently have a texture set" %}.<br />
{% endif %}
<br />
{% if DBaseObject|mmversion_lt:"1.2.3" %}
{% blocktrans with DBaseObject.id as serverid %}
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 />
{% endblocktrans %}
{% endif %}
</p>
<form action="{% url mumble.views.show DBaseObject.id %}" method="post" enctype="multipart/form-data">
<table>
{{ TextureForm }}
</table>
<input type="hidden" name="mode" value="texture" />
<input type="submit" />
</form>
{% endif %}
</div>
{% endif %}
{% if CurrentUserIsAdmin %}
<div id="mumble_admin" class="mumble-ext">
<h2>{% trans "Server administration" %}</h2>
<form action="{% url mumble.views.show DBaseObject.id %}" method="post">
<table>
{{ AdminForm }}
</table>
<input type="hidden" name="mode" value="admin" />
<input type="submit" />
</form>
</div>
{% endif %}
{% for item in ChannelTable %}
{% if item.is_player %}
<div id="mumble_{{ item.id }}" class="mumble-ext x-hide-display">
<h2>{% trans "Player" %} {{ item.name }}</h2>
<ul>
<li>{% trans "Online since" %}: {{ item.onlinesince|time }}</li>
<li>{% trans "Authenticated" %}: {{ item.isAuthed|yesno }}</li>
<li>{% trans "Admin" %}: {{ item.isAdmin|yesno }}</li>
<li>{% trans "Muted" %}: {{ item.mute|yesno }}</li>
<li>{% trans "Deafened" %}: {{ item.deaf|yesno }}</li>
<li>{% trans "Muted by self" %}: {{ item.selfMute|yesno }}</li>
<li>{% trans "Deafened by self" %}: {{ item.selfDeaf|yesno }}</li>
{% if CurrentUserIsAdmin or user.is_staff %}
<li>{% trans "IP Address" %}: <acronym title="{{ item.ipaddress }}">{{ item.fqdn }}</acronym></li>
{% endif %}
</ul>
{% if item.mumbleuser and item.mumbleuser.owner %}
<h2>{% trans "User" %} {{ item.mumbleuser.owner.username|capfirst }}</h2>
<ul>
{% if item.mumbleuser.owner.first_name and item.mumbleuser.owner.last_name %}
<li>{% trans "Full Name" %}: {{ item.mumbleuser.owner.first_name }} {{ item.mumbleuser.owner.last_name }}</li>
{% endif %}
<li>{% trans "Admin" %}: {{ item.mumbleuser.owner.is_staff|yesno }}</li>
<li>{% trans "Sign-up date" %}: {{ item.mumbleuser.owner.date_joined|date }}</li>
</ul>
{% endif %}
{% if item.comment %}
<fieldset>
<legend>{% trans "User Comment" %}</legend>
{{ item.comment|removetags:"script link meta html head body style"|safe }}
</fieldset>
{% endif %}
{% if item.mumbleuser and item.mumbleuser.hasTexture %}
<fieldset>
<legend>{% trans "User Texture" %}</legend>
<img src="{% url mumble.views.showTexture DBaseObject.id item.mumbleuser.id %}" alt="user texture" />
</fieldset>
{% endif %}
{% if CurrentUserIsAdmin or user.is_staff %}
<fieldset>
<legend>{% trans "Kick user" %}</legend>
<form action="{% url mumble.views.show DBaseObject.id %}" method="POST">
<input type="hidden" name="mode" value="kick" />
<input type="hidden" name="session" value="{{ item.session }}" />
<ul>
<li>
<label for="inp_reason">{% trans "Reason" %}</label>
<input type="text" name="reason" value="" id="inp_reason" />
</li>
<li>
<input type="checkbox" name="ban" value="1" id="inp_ban" />
<label for="inp_ban">{% trans "Ban user" %}</label>
</li>
</ul>
<input type="submit" value="{% trans "Kick user" %}" />
</form>
</fieldset>
{% endif %}
</div>
{% else %}
<div id="mumble_{{ item.id }}" class="mumble-ext x-hide-display">
<h2>{% trans "Channel" %} {{ item.name }}</h2>
{% if CurrentUserIsAdmin or user.is_staff %}
{% trans "Channel ID" %}: {{ item.chanid }}<br />
{% endif %}
<a href="{{ item|chanurl:MumbleAccount }}" class="mumble">{% trans "Connect" %}</a>
{% if item.description %}
<fieldset>
<legend>{% trans "Channel description" %}</legend>
{{ item.description|removetags:"script link meta html head body style"|safe }}
</fieldset>
{% endif %}
</div>
{% endif %}
{% endfor %}
{% endblock %}
{% block HeadTag %}
<script type="text/javascript">
Ext.onReady( function(){
Ext.get( 'mumble_registration' ).addClass( 'x-hide-display' );
{% if Registered %}
Ext.get( 'mumble_texture' ).addClass( 'x-hide-display' );
{% endif %}
{% if CurrentUserIsAdmin %}
Ext.get( 'mumble_admin' ).addClass( 'x-hide-display' );
{% endif %}
{% if CurrentUserIsAdmin %}
userRecord = Ext.data.Record.create([
{ name: 'id', type: 'int' },
{ name: 'name', type: 'string' },
{ name: 'password', type: 'string' },
{ name: 'owner', type: 'int' },
{ name: 'admin', type: 'bool' },
{ name: 'delete', type: 'bool' }
]);
userAdminStore = new Ext.data.Store({
url: '{% url mumble.views.users DBaseObject.id %}',
reader: new Ext.data.JsonReader({
root: 'objects',
fields: userRecord
}),
autoLoad: true,
remoteSort: false
});
adminColumn = new Ext.grid.CheckColumn({
header: '{% trans "Admin on root channel" %}',
dataIndex: 'admin',
width: 50
});
deleteColumn = new Ext.grid.CheckColumn({
header: '{% trans "Delete" %}',
dataIndex: 'delete',
width: 50
});
ownerCombo = new Ext.form.ComboBox({
name: 'owner',
hiddenName: 'owner_id',
forceSelection: true,
triggerAction: 'all',
valueField: 'uid',
displayField: 'uname',
store: new Ext.data.Store({
url: '{% url mumble.views.djangousers %}',
reader: new Ext.data.JsonReader({
fields: [ 'uid', 'uname' ],
root: 'objects'
}),
autoLoad: true
})
});
{% endif %}
var cardpanel = new Ext.Panel({
renderTo: 'mumble_ext_container',
layout: 'card',
id: 'mumble_container',
height: 570,
activeItem: 0,
border: false,
items: [ {
id: 'mumble_tabpanel',
xtype: 'tabpanel',
defaults: { autoheight: true },
activeTab: {{ DisplayTab }},
items: [
{ contentEl: 'mumble_motd', title: '{% trans "Server Info" %}', autoScroll: true },
{ contentEl: 'mumble_registration', title: '{% trans "Registration" %}', autoScroll: true,
{% if user.is_authenticated %}
listeners: {
activate: function(){ Ext.fly("id_name").focus() }
}
{% endif %}
},
{% if CurrentUserIsAdmin %}
{ contentEl: 'mumble_admin', title: '{% trans "Administration" %}', autoScroll: true },
{% endif %}
{% if Registered %}
{ contentEl: 'mumble_texture',title: '{% trans "User Texture" %}', autoScroll: true },
{% endif %}
{% if CurrentUserIsAdmin %}
{
title: '{% trans "User List" %}',
xtype: 'editorgrid',
store: userAdminStore,
cm: new Ext.grid.ColumnModel( [ {
header: '{% trans "name" %}',
dataIndex: 'name',
sortable: true,
editor: new Ext.form.TextField({
allowBlank: false
})
}, {
header: '{% trans "Account owner" %}',
dataIndex: 'owner',
editor: ownerCombo,
sortable: true,
renderer: function( value ){
if( value == '' ) return '';
items = ownerCombo.store.data.items;
for( i = 0; i < items.length; i++ )
if( items[i].data.uid == value )
return items[i].data.uname;
}
}, adminColumn, {
header: '{% trans "Change password" %}',
dataIndex: 'password',
editor: new Ext.form.TextField({
inputType: 'password'
}),
renderer: function( value ){
ret = '';
for( i = 0; i < value.length; i++ )
ret += '*';
return ret;
}
}, deleteColumn ] ),
tbar: [{
text: '{% trans "Add" %}',
handler : function(){
userAdminStore.add( new userRecord( {
id: -1,
name: 'New User',
admin: false,
owner: '',
password: '',
'delete': false
} ) );
}
}, {
text: '{% trans "Save" %}',
handler : function(){
data = [];
for( i = 0; i < userAdminStore.data.items.length; i++ ){
rec = userAdminStore.data.items[i];
if( rec.dirty ){
data.push(rec.data);
}
}
var conn = new Ext.data.Connection();
conn.request( {
url: userAdminStore.url,
params: { data: Ext.encode( data ) },
success: function(){
for( i = 0; i < userAdminStore.data.items.length; i++ ){
rec = userAdminStore.data.items[i];
if( rec.data['delete'] == true )
userAdminStore.remove( rec );
else if( rec.dirty ){
rec.commit();
}
}
}
} );
}
}, {
text: '{% trans "Resync with Murmur" %}',
handler: function(){
userAdminStore.reload({
params: { 'resync': 'true' }
});
}
}],
plugins: [ adminColumn, deleteColumn ]
}
{% endif %}
]
},
{% for item in ChannelTable %}
{ contentEl: 'mumble_{{ item.id }}', id: 'carditem_{{ item.id }}' }{% if not forloop.last %},{% endif %}
{% endfor %}
]
});
Ext.get( 'link_server' ).on( 'click', function( event, target ){
cardpanel.layout.setActiveItem( 'mumble_tabpanel' );
event.preventDefault();
});
{% for item in ChannelTable %}
Ext.get( 'link_{{ item.id }}' ).on( 'click', function( event, target ){
cardpanel.layout.setActiveItem( 'carditem_{{ item.id }}' );
event.preventDefault();
});
{% endfor %}
} );
</script>
<meta http-equiv="refresh" content="300" />
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "index.html" %}
{% comment %}
<!-- kate: space-indent on; indent-width 2; replace-tabs on; -->
{% endcomment %}
{% load i18n %}
{% load mumble_extras %}
{% block Headline %}
{{ DBaseObject.name }}
{% endblock %}
{% block Content %}
<div class="rahmen">
{% trans "This server is currently offline." %}
</div>
{% endblock %}

View File

@@ -0,0 +1,35 @@
{% comment %}
<!-- kate: space-indent on; indent-width 2; replace-tabs on; -->
{% endcomment %}
{% load mumble_extras %}
{% load i18n %}
<div class="mumble" style="background-image: url( {{ MEDIA_URL }}/mumble/linie_v.png )">
<span class="mumble">
{% if Player.isAuthed %}
<img src="{{ MEDIA_URL }}/mumble/authenticated.png" alt="authed" title="{% trans "Authenticated" %}" />
{% endif %}
{% if Player.mute %}
<img src="{{ MEDIA_URL }}/mumble/muted_server.png" alt="muted" title="{% trans "Muted" %}" />
{% endif %}
{% if Player.suppress %}
<img src="{{ MEDIA_URL }}/mumble/muted_suppressed.png" alt="muted" title="{% trans "Suppressed" %}" />
{% endif %}
{% if Player.deaf %}
<img src="{{ MEDIA_URL }}/mumble/deafened_server.png" alt="deafened" title="{% trans "Deafened" %}" />
{% endif %}
{% if Player.selfMute %}
<img src="{{ MEDIA_URL }}/mumble/muted_self.png" alt="self-muted" title="{% trans "Muted by self" %}" />
{% endif %}
{% if Player.selfDeaf %}
<img src="{{ MEDIA_URL }}/mumble/deafened_self.png" alt="self-deafened" title="{% trans "Deafened by self" %}" />
{% endif %}
{% if Player.hasComment %}
<img src="{{ MEDIA_URL }}/mumble/comment.png" alt="has comment" title="{% trans "has a User Comment set" %}" />
{% endif %}
</span>
<span>
<img src="{{ MEDIA_URL }}/mumble/knoten_v.png" alt="" />
<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>
</span>
</div>

View File

@@ -0,0 +1,14 @@
{% comment %}
<!-- kate: space-indent on; indent-width 2; replace-tabs on; -->
{% endcomment %}
{% load mumble_extras %}
<div style="margin-left: 20px;">
<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>
</div>
{% for sub in Server.rootchan.subchans %}
{% if sub.show %}
{{ sub|chanview:MumbleAccount }}
{% endif %}
{% endfor %}
{% for player in Server.rootchan.players %}{{ player|chanview }}{% endfor %}

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
"""
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
*
* Mumble-Django is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
"""

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
*
* Mumble-Django is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
"""
from django import template
from django.template.loader import render_to_string
from django.conf import settings
register = template.Library();
@register.filter
def trunc( string, maxlen = 50 ):
""" converts "a very very extaordinary long text" to "a very very extra... """
if len(string) < maxlen:
return string;
return string[:(maxlen - 3)] + "";
@register.filter
def chanview( obj, user = None ):
""" renders an mmChannel / mmPlayer object with the correct template """
if obj.is_server:
return render_to_string( 'mumble/server.html', { 'Server': obj, 'MumbleAccount': user, 'MEDIA_URL': settings.MEDIA_URL } );
elif obj.is_channel:
return render_to_string( 'mumble/channel.html', { 'Channel': obj, 'MumbleAccount': user, 'MEDIA_URL': settings.MEDIA_URL } );
elif obj.is_player:
return render_to_string( 'mumble/player.html', { 'Player': obj, 'MumbleAccount': user, 'MEDIA_URL': settings.MEDIA_URL } );
@register.filter
def chanurl( obj, user ):
""" create a connection URL and takes the user's login into account """
return obj.getURL( user );
@register.filter
def mmversion_lt( obj, version ):
""" return True if the given Server's version is less than the given version. """
return tuple(obj.version[:3]) < tuple([int(v) for v in version.split('.')])
@register.filter
def mmversion_eq( obj, version ):
""" return True if the given Server's version equals the given version. """
return tuple(obj.version[:3]) == tuple([int(v) for v in version.split('.')])
@register.filter
def mmversion_gt( obj, version ):
""" return True if the given Server's version is greater than the given version. """
return tuple(obj.version[:3]) > tuple([int(v) for v in version.split('.')])

99
mumble/testrunner.py Normal file
View File

@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
"""
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
*
* Mumble-Django is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
"""
import os
from django.test.simple import run_tests as django_run_tests
from django.conf import settings
from murmurenvutils import get_available_versions, run_callback, wait_for_user
def run_tests( test_labels, verbosity=1, interactive=True, extra_tests=[] ):
""" Run the Django built in testing framework, but before testing the mumble
app, allow Murmur to be set up correctly.
"""
if not test_labels:
test_labels = [ appname.split('.')[-1] for appname in settings.INSTALLED_APPS ];
# No need to sync any murmur servers for the other apps
os.environ['MURMUR_CONNSTR'] = '';
# The easy way: mumble is not being tested.
if "mumble" not in test_labels:
return django_run_tests( test_labels, verbosity, interactive, extra_tests );
# First run everything apart from mumble. mumble will be tested separately, so Murmur
# can be set up properly first.
failed_tests = 0;
if len(test_labels) > 1:
# only run others if mumble is not the only app to be tested
test_labels = list(test_labels);
test_labels.remove( "mumble" );
failed_tests += django_run_tests( test_labels, verbosity, interactive, extra_tests );
failed_tests += run_mumble_tests( verbosity, interactive );
return failed_tests;
def run_mumble_tests( verbosity=1, interactive=True ):
connstrings = {
'DBus': 'net.sourceforge.mumble.murmur',
'Ice': 'Meta:tcp -h 127.0.0.1 -p 6502',
};
def django_run_tests_wrapper( process, version ):
wr_failed_tests = 0;
for method in connstrings:
# Check if this server is ready to be used with the current method
if getattr( process.capabilities, ("has_%s" % method.lower()), False ):
print "Testing mumble %s via %s" % ( version, method );
os.environ['MURMUR_CONNSTR'] = connstrings[method];
settings.DEFAULT_CONN = connstrings[method];
settings.SLICE_VERSION = [ int(dgt) for dgt in version.split('.') ];
print "MURMUR_CONNSTR:", os.environ['MURMUR_CONNSTR'];
print "DEFAULT_CONN: ", settings.DEFAULT_CONN;
print "SLICE_VERSION: ", settings.SLICE_VERSION;
if not process.capabilities.has_users:
print "Waiting for user to connect (60 seconds)."
wait_for_user( process, timeout=60 );
wr_failed_tests += django_run_tests( ('mumble',), verbosity, interactive, [] );
else:
print "Mumble %s does not support Method %s" % ( version, method );
return wr_failed_tests;
failed_tests = 0;
from mctl import MumbleCtlBase
for version in get_available_versions():
MumbleCtlBase.clearCache();
run = raw_input( "Run tests for %s? [Y/n] " % version );
if run in ('Y', 'y', ''):
failed_tests += run_callback( version, django_run_tests_wrapper, version );
return failed_tests;

208
mumble/tests.py Normal file
View File

@@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
"""
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
*
* Mumble-Django is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
"""
from django.conf import settings
from django.test import TestCase
from models import Mumble
from utils import ObjectInfo
class InstancesHandling( TestCase ):
""" Tests creation, editing and removing of vserver instances. """
def setUp( self ):
# Make sure we always start with a FRESH murmur instance, checking for left-over instances
# and deleting them before creating ours.
try:
self.murmur = Mumble.objects.get( addr="0.0.0.0", port=31337 );
except Mumble.DoesNotExist:
pass
else:
self.murmur.delete();
finally:
self.murmur = Mumble( name="#unit testing instance#", addr="0.0.0.0", port=31337 );
self.murmur.save();
def testDefaultConf( self ):
conf = self.murmur.ctl.getAllConf( self.murmur.srvid );
self.assert_( type(conf) == dict );
self.assert_( "host" in conf );
self.assert_( "port" in conf );
self.assert_( "certificate" in conf );
self.assert_( "key" in conf );
self.assert_( "registerhostname" in conf );
self.assert_( "registername" in conf );
self.assert_( "channelname" in conf );
self.assert_( "username" in conf );
self.assert_( "obfuscate" in conf );
self.assert_( "defaultchannel" in conf );
def testAddrPortUnique( self ):
try:
duplicate = Mumble(
name="#another unit testing instance#",
addr=self.murmur.addr, port=self.murmur.port,
dbus=settings.DEFAULT_CONN
);
if duplicate.ctl.method == "ICE":
import Murmur
self.assertRaises( Murmur.ServerFailureException, duplicate.save );
elif self.murmur.version[:2] == [ 1, 2 ]:
from dbus import DBusException
self.assertRaises( DBusException, duplicate.save );
else:
from sqlite3 import IntegrityError
self.assertRaises( IntegrityError, duplicate.save );
finally:
# make sure the duplicate is removed
duplicate.ctl.deleteServer( duplicate.srvid );
def tearDown( self ):
self.murmur.delete();
class DataReading( TestCase ):
""" Tests reading data from murmur using the low-level CTL methods. """
def setUp( self ):
# BIG FAT WARNING: This sucks ass, because it assumes the tester has a
# Murmur database like the one I have.
# I definitely need to prepare Murmur somehow before running these tests.
# Just don't yet know how.
self.murmur = Mumble.objects.get(id=1);
def testCtlGetChannels( self ):
""" Test getChannels() """
channels = self.murmur.ctl.getChannels( self.murmur.srvid );
if self.murmur.ctl.method == "ICE":
import Murmur
self.assertEquals( type( channels[0] ), Murmur.Channel );
else:
self.assertEquals( type( channels[0] ), ObjectInfo );
self.assert_( hasattr( channels[0], "id" ) );
self.assert_( hasattr( channels[0], "name" ) );
self.assert_( hasattr( channels[0], "parent" ) );
self.assert_( hasattr( channels[0], "links" ) );
def testCtlGetPlayers( self ):
""" Test getPlayers() """
players = self.murmur.ctl.getPlayers( self.murmur.srvid );
self.assert_( len(players) > 0 );
self.assertEquals( type(players), dict );
for plidx in players:
player = players[plidx];
if self.murmur.ctl.method == "ICE" and self.murmur.version[:2] == ( 1, 2 ):
import Murmur
self.assertEquals( type( player ), Murmur.User );
else:
self.assertEquals( type( player ), ObjectInfo );
self.assert_( hasattr( player, "session" ) );
self.assert_( hasattr( player, "mute" ) );
self.assert_( hasattr( player, "deaf" ) );
self.assert_( hasattr( player, "selfMute" ) );
self.assert_( hasattr( player, "selfDeaf" ) );
self.assert_( hasattr( player, "channel" ) );
self.assert_( hasattr( player, "userid" ) );
self.assert_( hasattr( player, "name" ) );
self.assert_( hasattr( player, "onlinesecs" ) );
self.assert_( hasattr( player, "bytespersec" ) );
def testCtlGetRegisteredPlayers( self ):
""" Test getRegistredPlayers() and getRegistration() """
players = self.murmur.ctl.getRegisteredPlayers( self.murmur.srvid );
self.assert_( len(players) > 0 );
self.assertEquals( type(players), dict );
for plidx in players:
player = players[plidx];
self.assertEquals( type( player ), ObjectInfo );
self.assert_( hasattr( player, "userid" ) );
self.assert_( hasattr( player, "name" ) );
self.assert_( hasattr( player, "email" ) );
self.assert_( hasattr( player, "pw" ) );
# compare with getRegistration result
reg = self.murmur.ctl.getRegistration( self.murmur.srvid, player.userid );
self.assertEquals( type( reg ), ObjectInfo );
self.assert_( hasattr( reg, "userid" ) );
self.assert_( hasattr( reg, "name" ) );
self.assert_( hasattr( reg, "email" ) );
self.assert_( hasattr( reg, "pw" ) );
self.assertEquals( player.userid, reg.userid );
self.assertEquals( player.name, reg.name );
self.assertEquals( player.email, reg.email );
self.assertEquals( player.pw, reg.pw );
def testCtlGetAcl( self ):
""" Test getACL() for the root channel """
acls, groups, inherit = self.murmur.ctl.getACL( self.murmur.srvid, 0 );
for rule in acls:
if self.murmur.ctl.method == "ICE" and self.murmur.version[:2] == ( 1, 2 ):
import Murmur
self.assertEquals( type( rule ), Murmur.ACL );
else:
self.assertEquals( type( rule ), ObjectInfo );
self.assert_( hasattr( rule, "applyHere" ) );
self.assert_( hasattr( rule, "applySubs" ) );
self.assert_( hasattr( rule, "inherited" ) );
self.assert_( hasattr( rule, "userid" ) );
self.assert_( hasattr( rule, "group" ) );
self.assert_( hasattr( rule, "allow" ) );
self.assert_( hasattr( rule, "deny" ) );
for grp in groups:
if self.murmur.ctl.method == "ICE":
import Murmur
self.assertEquals( type( grp ), Murmur.Group );
else:
self.assertEquals( type( grp ), ObjectInfo );
self.assert_( hasattr( grp, "name" ) );
self.assert_( hasattr( grp, "inherited" ) );
self.assert_( hasattr( grp, "inherit" ) );
self.assert_( hasattr( grp, "inheritable" ) );
self.assert_( hasattr( grp, "add" ) );
self.assert_( hasattr( grp, "remove" ) );
self.assert_( hasattr( grp, "members" ) );

35
mumble/urls.py Normal file
View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
"""
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
*
* Mumble-Django is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
"""
from django.conf.urls.defaults import patterns
urlpatterns = patterns(
'mumble.views',
( r'djangousers', 'djangousers' ),
( r'(?P<server>\d+)/users', 'users' ),
( r'(?P<server>\d+)/(?P<userid>\d+)/texture.png', 'showTexture' ),
( r'murmur/tree/(?P<server>\d+)', 'mmng_tree' ),
( r'mumbleviewer/(?P<server>\d+).xml', 'mumbleviewer_tree_xml' ),
( r'mumbleviewer/(?P<server>\d+).json', 'mumbleviewer_tree_json'),
( r'mobile/(?P<server>\d+)', 'mobile_show' ),
( r'mobile/?$', 'mobile_mumbles' ),
( r'(?P<server>\d+)', 'show' ),
( r'$', 'mumbles' ),
)

408
mumble/views.py Normal file
View File

@@ -0,0 +1,408 @@
# -*- coding: utf-8 -*-
"""
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
*
* Mumble-Django is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
"""
import simplejson
from StringIO import StringIO
from PIL import Image
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404
from django.template import RequestContext
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
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 forms import MumbleForm, MumbleUserForm, MumbleUserPasswordForm
from forms import MumbleUserLinkForm, MumbleTextureForm, MumbleKickForm
def redir( request ):
""" Redirect to the servers list. """
if request.META['HTTP_USER_AGENT'].startswith( 'BlackBerry' ) or \
"Opera Mobi" in request.META['HTTP_USER_AGENT'] or \
"Opera Mini" in request.META['HTTP_USER_AGENT'] or \
"Windows CE" in request.META['HTTP_USER_AGENT'] or \
"MIDP" in request.META['HTTP_USER_AGENT'] or \
"Palm" in request.META['HTTP_USER_AGENT'] or \
"NetFront" in request.META['HTTP_USER_AGENT'] or \
"Nokia" in request.META['HTTP_USER_AGENT'] or \
"Symbian" in request.META['HTTP_USER_AGENT'] or \
"UP.Browser" in request.META['HTTP_USER_AGENT'] or \
"UP.Link" in request.META['HTTP_USER_AGENT'] or \
"WinWAP" in request.META['HTTP_USER_AGENT'] or \
"Android" in request.META['HTTP_USER_AGENT'] or \
"DoCoMo" in request.META['HTTP_USER_AGENT'] or \
"KDDI-" in request.META['HTTP_USER_AGENT'] or \
"Softbank" in request.META['HTTP_USER_AGENT'] or \
"J-Phone" in request.META['HTTP_USER_AGENT'] or \
"IEMobile" in request.META['HTTP_USER_AGENT'] or \
"iPod" in request.META['HTTP_USER_AGENT'] or \
"iPhone" in request.META['HTTP_USER_AGENT']:
return HttpResponseRedirect( reverse( mobile_mumbles ) );
else:
return HttpResponseRedirect( reverse( mumbles ) );
def mobile_mumbles( request ):
return mumbles( request, mobile=True );
def mumbles( request, mobile=False ):
""" Display a list of all configured Mumble servers, or redirect if only one configured. """
mms = Mumble.objects.all().order_by( "name" );
if len(mms) == 1:
return HttpResponseRedirect( reverse(
{ False: show, True: mobile_show }[mobile],
kwargs={ 'server': mms[0].id, }
) );
return render_to_response(
'mumble/%s.html' % { False: 'list', True: 'mobile_list' }[mobile],
{ 'MumbleObjects': mms,
'MumbleActive': True,
},
context_instance = RequestContext(request)
);
def show( request, server ):
""" Display the channel list for the given Server ID.
This includes not only the channel list itself, but indeed the user registration,
server admin and user texture form as well. The template then uses JavaScript
to display these forms integrated into the Channel viewer.
"""
srv = get_object_or_404( Mumble, id=server );
if not srv.booted:
return render_to_response(
'mumble/offline.html',
{ 'DBaseObject': srv,
'MumbleActive': True,
}, context_instance = RequestContext(request) );
isAdmin = srv.isUserAdmin( request.user );
# The tab to start on.
displayTab = 0;
if isAdmin:
if request.method == 'POST' and "mode" in request.POST and request.POST['mode'] == 'admin':
adminform = MumbleForm( request.POST, instance=srv );
if adminform.is_valid():
adminform.save();
return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) );
else:
displayTab = 2;
else:
adminform = MumbleForm( instance=srv );
else:
adminform = None;
registered = False;
user = None;
if request.user.is_authenticated():
# Unregistered users may or may not need a password to register.
if settings.PROTECTED_MODE and srv.passwd:
unregged_user_form = MumbleUserPasswordForm;
# Unregistered users may or may not want to link an existing account
elif settings.ALLOW_ACCOUNT_LINKING:
unregged_user_form = MumbleUserLinkForm;
else:
unregged_user_form = MumbleUserForm;
if request.method == 'POST' and 'mode' in request.POST and request.POST['mode'] == 'reg':
try:
user = MumbleUser.objects.get( server=srv, owner=request.user );
except MumbleUser.DoesNotExist:
regform = unregged_user_form( request.POST );
regform.server = srv;
if regform.is_valid():
model = regform.save( commit=False );
model.owner = request.user;
model.server = srv;
# If we're linking accounts, the change is local only.
model.save( dontConfigureMurmur=( "linkacc" in regform.data ) );
return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) );
else:
displayTab = 1;
else:
regform = MumbleUserForm( request.POST, instance=user );
regform.server = srv;
if regform.is_valid():
regform.save();
return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) );
else:
displayTab = 1;
else:
try:
user = MumbleUser.objects.get( server=srv, owner=request.user );
except MumbleUser.DoesNotExist:
regform = unregged_user_form();
else:
regform = MumbleUserForm( instance=user );
registered = True;
if request.method == 'POST' and 'mode' in request.POST and request.POST['mode'] == 'texture' and registered:
textureform = MumbleTextureForm( request.POST, request.FILES );
if textureform.is_valid():
user.setTexture( Image.open( request.FILES['texturefile'] ) );
return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) );
else:
textureform = MumbleTextureForm();
else:
regform = None;
textureform = None;
if isAdmin:
if request.method == 'POST' and 'mode' in request.POST and request.POST['mode'] == 'kick':
kickform = MumbleKickForm( request.POST );
if kickform.is_valid():
if kickform.cleaned_data["ban"]:
srv.banUser( kickform.cleaned_data['session'], kickform.cleaned_data['reason'] );
srv.kickUser( kickform.cleaned_data['session'], kickform.cleaned_data['reason'] );
# ChannelTable is a somewhat misleading name, as it actually contains channels and players.
channelTable = [];
for cid in srv.channels:
if cid != 0 and srv.channels[cid].show:
channelTable.append( srv.channels[cid] );
for pid in srv.players:
channelTable.append( srv.players[pid] );
show_url = reverse( show, kwargs={ 'server': srv.id } );
login_url = reverse( auth_views.login );
return render_to_response(
'mumble/mumble.html',
{
'login_url': "%s?next=%s" % ( login_url, show_url ),
'DBaseObject': srv,
'ChannelTable': channelTable,
'CurrentUserIsAdmin': isAdmin,
'AdminForm': adminform,
'RegForm': regform,
'TextureForm': textureform,
'Registered': registered,
'DisplayTab': displayTab,
'MumbleActive': True,
'MumbleAccount':user,
},
context_instance = RequestContext(request)
);
def mobile_show( request, server ):
""" Display the channel list for the given Server ID. """
srv = get_object_or_404( Mumble, id=server );
user = None;
if request.user.is_authenticated():
try:
user = MumbleUser.objects.get( server=srv, owner=request.user );
except MumbleUser.DoesNotExist:
pass;
return render_to_response(
'mumble/mobile_mumble.html',
{
'DBaseObject': srv,
'MumbleActive': True,
'MumbleAccount':user,
},
context_instance = RequestContext(request)
);
def showTexture( request, server, userid ):
""" Pack the given user's texture into an HttpResponse.
If userid is none, use the currently logged in User.
"""
srv = get_object_or_404( Mumble, id=int(server) );
user = get_object_or_404( MumbleUser, server=srv, id=int(userid) );
try:
img = user.getTexture();
except ValueError:
raise Http404();
else:
buf = StringIO();
img.save( buf, "PNG" );
return HttpResponse( buf.getvalue(), "image/png" );
@login_required
def users( request, server ):
""" Create a list of MumbleUsers for a given server serialized as a JSON object.
If the request has a "data" field, evaluate that and update the user records.
"""
srv = get_object_or_404( Mumble, id=int(server) );
if "resync" in request.POST and request.POST['resync'] == "true":
srv.readUsersFromMurmur();
if not srv.isUserAdmin( request.user ):
return HttpResponse(
simplejson.dumps({ 'success': False, 'objects': [], 'errormsg': 'Access denied' }),
mimetype='text/javascript'
);
if request.method == 'POST':
data = simplejson.loads( request.POST['data'] );
for record in data:
if record['id'] == -1:
if record['delete']:
continue;
mu = MumbleUser( server=srv );
else:
mu = MumbleUser.objects.get( id=record['id'] );
if record['delete']:
mu.delete();
continue;
mu.name = record['name'];
mu.password = record['password'];
if record['owner']:
mu.owner = User.objects.get( id=int(record['owner']) );
mu.save();
mu.aclAdmin = record['admin'];
users = [];
for mu in srv.mumbleuser_set.all():
owner = None;
if mu.owner is not None:
owner = mu.owner.id
users.append( {
'id': mu.id,
'name': mu.name,
'password': None,
'owner': owner,
'admin': mu.aclAdmin,
} );
return HttpResponse(
simplejson.dumps( { 'success': True, 'objects': users } ),
mimetype='text/javascript'
);
@login_required
def djangousers( request ):
""" Return a list of all Django users' names and IDs. """
users = [ { 'uid': '', 'uname': '------' } ];
for du in User.objects.all().order_by( 'username' ):
users.append( {
'uid': du.id,
'uname': unicode( du ),
} );
return HttpResponse(
simplejson.dumps( { 'success': True, 'objects': users } ),
mimetype='text/javascript'
);
def mmng_tree( request, server ):
""" Return a JSON representation of the channel tree suitable for
Murmur Manager:
http://github.com/cheald/murmur-manager/tree/master/widget/
To make the client widget query this view, set the URL attribute
to "http://<mumble-django base URL>/mumble"
"""
srv = get_object_or_404( Mumble, id=int(server) );
chanlist = []
userlist = []
for chanid in srv.channels:
channel = srv.channels[chanid]
if channel.parent is not None:
parent = channel.parent.chanid
else:
parent = -1
chanlist.append({
"type": "channel",
"id": channel.chanid,
"name": channel.name,
"parent": parent,
"position": channel.position,
"state": channel.temporary and "temporary" or "permanent"
})
for sessionid in srv.players:
user = srv.players[sessionid]
userlist.append({
"type": "player",
"name": user.name,
"channel": user.channel.chanid,
"mute": user.mute or user.selfMute or user.suppress,
"deaf": user.deaf or user.selfDeaf,
"online": user.onlinesecs,
"state": "online"
})
if "callback" in request.GET:
prefix = request.GET["callback"]
else:
prefix = ""
return HttpResponse(
prefix + "(" + simplejson.dumps( { 'channels': chanlist, 'users': userlist } ) + ")",
mimetype='text/javascript'
);
def mumbleviewer_tree_xml( request, server ):
""" Get the XML tree from the server and serialize it to the client. """
from xml.etree.cElementTree import tostring as xml_to_string
srv = get_object_or_404( Mumble, id=int(server) );
return HttpResponse(
xml_to_string( srv.asMvXml(), encoding='utf-8' ),
mimetype='text/xml'
);
def mumbleviewer_tree_json( request, server ):
""" Get the Dict from the server and serialize it as JSON to the client. """
srv = get_object_or_404( Mumble, id=int(server) );
if "jsonp_callback" in request.GET:
prefix = request.GET["jsonp_callback"]
else:
prefix = ""
return HttpResponse(
prefix + "(" + simplejson.dumps( srv.asMvJson() ) + ")",
mimetype='text/javascript'
);

View File

@@ -5,8 +5,8 @@ from reddit.forms import RedditAccountForm
from datetime import date from datetime import date
class RedditAccountAdmin(admin.ModelAdmin): class RedditAccountAdmin(admin.ModelAdmin):
list_display = ('username', 'user', 'date_created', 'link_karma', 'comment_karma', 'last_update', 'is_valid') list_display = ('username', 'user', 'date_created', 'link_karma', 'comment_karma', 'last_update', 'validated', 'is_valid')
search_fields = ['username', 'user'] search_fields = ['username']
fields = ('user', 'username') fields = ('user', 'username')

View File

@@ -1,29 +1,39 @@
import time import time
import datetime
import logging import logging
import settings
from django_cron import cronScheduler, Job
from reddit.models import RedditAccount from reddit.models import RedditAccount
from reddit.api import Inbox from reddit.api import Inbox
class UpdateAPIs(Job): class UpdateAPIs():
""" """
Updates all Reddit API elements in the database Updates all Reddit API elements in the database
""" """
# run every 24 hours
run_every = 86400
@property @property
def _logger(self): def _logger(self):
if not hasattr(self, '__logger'): if not hasattr(self, '__logger'):
self.__logger = logging.getLogger(__name__) self.__logger = logging.getLogger(__name__)
return self.__logger return self.__logger
last_update_delay = 604800
def job(self): def job(self):
for acc in RedditAccount.objects.all(): delta = datetime.timedelta(seconds=self.last_update_delay)
print delta
self._logger.debug("Updating accounts older than %s" % (datetime.datetime.now() - delta))
for acc in RedditAccount.objects.filter(last_update__lt=(datetime.datetime.now() - delta)):
self._logger.info("Updating %s" % acc.username)
try:
acc.api_update() acc.api_update()
except RedditAccount.DoesNotExist:
acc.delete()
else:
acc.save() acc.save()
time.sleep(2) time.sleep(.5)
class APIKeyParser: class APIKeyParser:
@@ -43,22 +53,21 @@ class APIKeyParser:
def __str__(self): def __str__(self):
return "%s:%s" % (self.user_id, self.api_key) return "%s:%s" % (self.user_id, self.api_key)
class ProcessInbox(Job): class ProcessValidations():
""" """
Grabs all Reddit Mail and processes any new applications Grabs all Reddit Mail and processes validations
""" """
def job(self): def job(self):
inbox = Inbox(settings.REDDIT_USER, settings.REDDIT_PASSWORD) inbox = Inbox(settings.REDDIT_USER, settings.REDDIT_PASSWD)
for msg in inbox: for msg in inbox:
if not msg.was_comment and msg.new: if not msg.was_comment and msg.new:
try: try:
key = APIKeyParser(msg.body) acc = RedditAccount.objects.get(username__iexact=msg.author)
except: if not acc.validated and msg.body == acc.user.username:
pass acc.validated = True
else: acc.save()
print key.username except RedditAccount.DoesNotExist:
continue
cronScheduler.register(UpdateAPIs)

View File

@@ -0,0 +1 @@
SEQUENCE = ['validation-field']

View File

@@ -0,0 +1,7 @@
from django_evolution.mutations import *
from django.db import models
MUTATIONS = [
AddField('RedditAccount', 'validated', models.BooleanField, initial=False),
]

View File

@@ -14,11 +14,11 @@ class RedditAccount(models.Model):
username = models.CharField("Reddit Username", max_length=32, blank=False) username = models.CharField("Reddit Username", max_length=32, blank=False)
reddit_id = models.CharField("Reddit ID", max_length=32) reddit_id = models.CharField("Reddit ID", max_length=32)
date_created = models.DateTimeField("Date Created")
link_karma = models.IntegerField("Link Karma") link_karma = models.IntegerField("Link Karma")
comment_karma = models.IntegerField("Comment Karma") comment_karma = models.IntegerField("Comment Karma")
validated = models.BooleanField("Validated")
date_created = models.DateTimeField("Date Created")
last_update = models.DateTimeField("Last Update from API") last_update = models.DateTimeField("Last Update from API")
def api_update(self): def api_update(self):
@@ -32,10 +32,23 @@ class RedditAccount(models.Model):
self.link_karma = data['link_karma'] self.link_karma = data['link_karma']
self.comment_karma = data['comment_karma'] self.comment_karma = data['comment_karma']
self.reddit_id = data['id'] self.reddit_id = data['id']
self.date_created = datetime.fromtimestamp(data['created_utc'])
self.date_created = datetime.fromtimestamp(data['created_utc'])
self.last_update = datetime.now() self.last_update = datetime.now()
def recent_posts(self):
try:
jsondoc = json.load(urllib.urlopen("http://reddit.com/user/%s.json" % self.username))
except:
raise self.DoesNotExist
posts = []
for item in jsondoc['data']['children']:
posts.append(item['data'])
return posts
class Meta: class Meta:
app_label = 'reddit' app_label = 'reddit'
ordering = ['username'] ordering = ['username']

0
registration/__init__.py Normal file
View File

11
registration/admin.py Normal file
View File

@@ -0,0 +1,11 @@
from django.contrib import admin
from registration.models import RegistrationProfile
class RegistrationAdmin(admin.ModelAdmin):
list_display = ('__unicode__', 'activation_key_expired')
search_fields = ('user__username', 'user__first_name')
admin.site.register(RegistrationProfile, RegistrationAdmin)

20
registration/cron.py Normal file
View File

@@ -0,0 +1,20 @@
import logging
from registration.models import RegistrationProfile
class RemoveExpiredProfiles():
"""
Deletes expired profile requests
"""
# run every 2 hours
run_every = 7200
@property
def _logger(self):
if not hasattr(self, '__logger'):
self.__logger = logging.getLogger(__name__)
return self.__logger
def job(self):
RegistrationProfile.objects.delete_expired_users()

Some files were not shown because too many files have changed in this diff Show More