from forum.models.action import ActionProxy
from forum.models import Award
from forum import settings
+from forum.settings import APP_SHORT_NAME
class UserJoinsAction(ActionProxy):
def repute_users(self):
self.repute(self.user, int(settings.INITIAL_REP))
+ def describe(self, viewer=None):
+ return _("%(user)s as joined the %(app_name)s Q&A community") % {
+ 'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),
+ 'app_name': APP_SHORT_NAME,
+ }
+
class EditProfileAction(ActionProxy):
- pass
+ def describe(self, viewer=None):
+ return _("%(user)s edited %(hes_or_your)s %(profile_link)s") % {
+ 'user': self.hyperlink(self.user.get_profile_url(), self.friendly_username(viewer, self.user)),
+ 'hes_or_your': self.viewer_or_user_verb(viewer, self.user, _('your'), _('hes')),
+ 'profile_link': self.hyperlink(self.user.get_profile_url(), _('profile')),
+ }
class AwardAction(ActionProxy):
def process_data(self, badge, trigger):
return ""\r
\r
def describe(self, viewer=None):\r
- return ""\r
+ return self.__class__.__name__\r
\r
def get_absolute_url(self):\r
if self.node:\r
from django.utils.version import get_svn_revision
OSQA_VERSION = "Development Build"
-SVN_REVISION = get_svn_revision(djsettings.SITE_SRC_ROOT)
+SVN_REVISION = get_svn_revision(djsettings.SITE_SRC_ROOT)
+
SETTINGS_PACK = Setting('SETTINGS_PACK', "default")
+DJSTYLE_ADMIN_INTERFACE = Setting('DJSTYLE_ADMIN_INTERFACE', True)
+
APP_URL = djsettings.APP_URL
FORUM_SCRIPT_ALIAS = djsettings.FORUM_SCRIPT_ALIAS
help_text = _("The description of your application"),\r
widget=Textarea))\r
\r
-APP_INTRO = Setting('APP_INTRO', u'<p>Ask and answer questions, make the world better!</p>', BASIC_SET, dict(\r
-label = _("Application intro"),\r
-help_text = _("The introductory page that is visible in the sidebar for anonymous users."),\r
-widget=Textarea))\r
-\r
APP_COPYRIGHT = Setting('APP_COPYRIGHT', u'Copyright OSQA, 2010. Some rights reserved under creative commons license.', BASIC_SET, dict(\r
label = _("Copyright notice"),\r
help_text = _("The copyright notice visible at the footer of your page.")))\r
help_text = _("If you have a specific place to get feedback from your users, use this field and the fedback link on the footer will point there."),\r
required=False))\r
\r
-SHOW_WELCOME_BOX = Setting('SHOW_WELCOME_BOX', True, BASIC_SET, dict(\r
-label = _("Show the Welcome box"),\r
-help_text = _("Do you want to show the welcome box when a user first visits your site."),\r
-required=False))\r
for setting in set:
if isinstance(setting, (Setting.emulators.get(str, DummySetting), Setting.emulators.get(unicode, DummySetting))):
+ if not setting.field_context.get('widget', None):
+ setting.field_context['widget'] = forms.TextInput(attrs={'class': 'longstring'})
field = forms.CharField(**setting.field_context)
elif isinstance(setting, Setting.emulators.get(float, DummySetting)):
field = forms.FloatField(**setting.field_context)
from base import Setting, SettingSet
from django.forms.widgets import Textarea
+from django.utils.translation import ugettext_lazy as _
SIDEBAR_SET = SettingSet('sidebar', 'Sidebar content', "Enter contents to display in the sidebar. You can use markdown and some basic html tags.", 10, True)
+SHOW_WELCOME_BOX = Setting('SHOW_WELCOME_BOX', True, SIDEBAR_SET, dict(
+label = _("Show the Welcome box"),
+help_text = _("Do you want to show the welcome box when a user first visits your site."),
+required=False))
+
+APP_INTRO = Setting('APP_INTRO', u'<p>Ask and answer questions, make the world better!</p>', SIDEBAR_SET, dict(
+label = _("Application intro"),
+help_text = _("The introductory page that is visible in the sidebar for anonymous users."),
+widget=Textarea))
+
+
SIDEBAR_UPPER_SHOW = Setting('SIDEBAR_UPPER_SHOW', True, SIDEBAR_SET, dict(
label = "Show Upper Block",
help_text = "Check if your pages should display the upper sidebar block.",
.string_list_widget_button.add {
position: relative;
left: 554px;
+}
+
+table caption {
+ font-size: 120%;
+ padding: 3px 0;
+ text-align: left;
}
\ No newline at end of file
--- /dev/null
+textarea {
+ width: 100%;
+}
+
+input.longstring {
+ width: 100%;
+}
\ No newline at end of file
{% trans "User login" %}\r
</div>\r
{% if msg %}\r
- <p class="warning">{{ msg }}</p>\r
+ <p class="error">{{ msg }}</p>\r
{% endif %}\r
{% for provider in top_stackitem_providers %}\r
<form class="signin_form" method="POST" action="{% url auth_provider_signin provider=provider.id %}">\r
+++ /dev/null
-{% extends "base_content.html" %}
-<!--signup.html-->
-{% load i18n %}
-{% block title %}{% spaceless %}{% trans "Signup" %}{% endspaceless %}{% endblock %}
-
-{% block content %}
-<div class="headNormal">
- {% trans "Create login name and password" %}
-</div>
-<p class="message">{% trans "Traditional signup info" %}</p>
-<form action="" method="post" accept-charset="utf-8">
- <ul class="form-horizontal-rows">
- <li><label for="usename_id">{{form.username.label}}</label>{{form.username}}{{form.username.errors}}</li>
- <li><label for="email_id">{{form.email.label}}</label>{{form.email}}{{form.email.errors}}</li>
- <li><label for="password1_id">{{form.password1.label}}</label>{{form.password1}}{{form.password1.errors}}</li>
- <li><label for="password2_id">{{form.password2.label}}</label>{{form.password2}}{{form.password2.errors}}</li>
- </ul>
- <p class="margin-top">{% trans "receive updates motivational blurb" %}</p>
- <div class='simple-subscribe-options'>
- {{ email_feeds_form.subscribe }}
- {% if email_feeds_form.errors %}
- <p class="error">{% trans "please select one of the options above" %}</p>
- {% endif %}
- </div>
- <div>
- {% for field in form.anti_spam_fields %}
- <div>{{ field }}</div>
- <p class="error">{{ field.errors }}</p>
- {% endfor %}
- </div>
- <div class="submit-row"><input type="submit" class="submit" value="{% trans "Create Account" %}" />
- <strong>{% trans "or" %}
- <a href="{% url auth_signin %}">{% trans "return to login page" %}</a></strong></div>
-</form>
-{% endblock %}
-<!--end signup.html-->
\r
{% block sidebar %}\r
<div class="boxC">\r
+ <a href="{% url admin_switch_interface %}?to=djstyle">{% trans "Switch to django style interface" %}</a>\r
<h3 class="subtitle">{% trans "Administration menu" %}</h3>\r
<ul>\r
{% for set in sets %}\r
--- /dev/null
+{% extends basetemplate %}
+
+{% load i18n %}
+{% load user_tags %}
+
+{% block subtitle %}
+ {% trans "Dashboard" %}
+{% endblock %}
+{% block description %}
+ {% trans "Welcome to the OSQA administration area." %}
+{% endblock %}
+
+{% block admincontent %}
+ <div class="module" style="width:49%; display: inline-block; vertical-align: top;">
+ <table style="width: 100%; height: 100%;">
+ <caption>{% trans "Quick statistics" %}</caption>
+ <tr>
+ <td>
+ {{ statistics.total_questions }} {% trans "question" %}{{ statistics.total_questions|pluralize }} ({{ statistics.questions_last_24 }} {% trans "in the last 24 hours" %})
+ </td>
+ </tr>
+ <tr>
+ <td>
+ {{ statistics.total_answers }} {% trans "answer" %}{{ statistics.total_answers|pluralize }} ({{ statistics.answers_last_24 }} {% trans "in the last 24 hours" %})
+ </td>
+ </tr>
+ <tr>
+ <td>
+ {{ statistics.total_users }} {% trans "user" %}{{ statistics.total_users|pluralize }} ({{ statistics.users_last_24 }} {% trans "joined in the last 24 hours" %})
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div class="module" style="width:49%; display: inline-block;">
+ <table>
+ <caption>{%trans "Site status" %}</caption>
+ <tr>
+ <td>
+ {% ifequal settings_pack "bootstrap" %}
+ {% trans "Your site is running in bootstrap mode, click the button below to revert to defaults." %}<br />
+ {% else %}
+ {% ifequal settings_pack "default" %}
+ {% trans "Your site is running in standard mode, click the button below to run in bootstrap mode." %}<br />
+ {% else %}
+ {% trans "Your site is running with some customized settings, click the buttons below to run with defaults or in bootstrap mode" %}
+ {% endifequal %}
+ {% endifequal %}
+ {% ifnotequal settings_pack "default" %}
+ <button onclick="if (window.confirm('{% trans "Are you sure you want to revert to the defaults?" %}')) window.location='{% url admin_go_defaults %}';">{% trans "revert to defaults" %}</button>
+ {% endifnotequal %}
+ {% ifnotequal settings_pack "bootstrap" %}
+ <button onclick="if (window.confirm('{% trans "Are you sure you want to run bootstrap mode?" %}')) window.location='{% url admin_go_bootstrap %}';">{% trans "go bootstrap" %}</button>
+ {% endifnotequal %}
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <em>"Bootstrap mode" relaxes the minimum required reputation to perform actions like voting and commenting.
+ This is useful to help new communities get started.</em>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div class="module" style="width:98%; display: inline-block;">
+ <table width="100%">
+ <caption>{% trans "Recent activity" %}</caption>
+ <tr>
+ <td colspan="2">
+ <table width="100%">
+ {% for activity in recent_activity %}
+ <tr><td>{% activity_item activity request.user %}</td></tr>
+ {% endfor %}
+ </table>
+ </td>
+ </tr>
+ </table>
+ </div>
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% load extra_tags %}
+{% load i18n %}
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <link rel="stylesheet" href="{{ settings.ADMIN_MEDIA_PREFIX }}css/base.css" type="text/css"/>
+ <link rel="stylesheet" href="{{ settings.ADMIN_MEDIA_PREFIX }}css/forms.css" type="text/css"/>
+ <link rel="stylesheet" href="{{ settings.ADMIN_MEDIA_PREFIX }}css/changelists.css" type="text/css"/>
+ <link rel="stylesheet" type="text/css" media="screen" href="{% media "/media/style/djstyle_admin.css" %}"/>
+ <script src="http://www.google.com/jsapi" type="text/javascript"></script>
+ <script type="text/javascript">
+ //<!--
+ google.load("jquery", "1.3");
+ //-->
+ </script>
+ <script type="text/javascript" src="{% media "/media/js/osqa.admin.js" %}"></script>
+ {% block adminjs %}{% endblock %}
+ <title>{% trans "OSQA administration area" %} - {% block subtitle %}{% endblock %}</title>
+</head>
+<body>
+<div id="container">
+ <div id="header">
+ {% block header %}
+ <div id="branding">
+ <h1 id="site-name"><a href="{% url index %}">{{ settings.APP_SHORT_NAME }} - {% trans "Administration Area" %}</a> </h1>
+ </div>
+ {% endblock %}
+ <div id="user-tools">
+ {% trans "Welcome," %}
+ <strong>{{ request.user.username }}</strong>.
+ <a href="{% url admin_switch_interface %}?to=default">{% trans "To standard interface" %}</a> / <a href="{% url logout %}">{% trans "Log out" %}</a>
+ </div>
+ </div>
+ <div class="breadcrumbs">
+ <a href="{% url index %}">{% trans "Home" %}</a> >
+ <a href="{% url admin_index %}">{% trans "Dashboard" %}</a> >
+ {% block pagename %}{% endblock %} -
+ {% block description %}{% endblock %}
+ </div>
+ <div id="content" class="colMS">
+ <div id="content-main">
+ {% block admincontent %}{% endblock %}
+ </div>
+ <div id="content-related">
+ <div id="basic-sets-menu" class="module">
+ <h2>{% trans "Basic settings" %}</h2>
+ <ul>
+ <li><a href="{% url admin_set allsets.basic.name %}">{{ allsets.basic.title }}</a></li>
+ <li><a href="{% url admin_set allsets.users.name %}">{{ allsets.users.title }}</a></li>
+ <li><a href="{% url admin_set allsets.email.name %}">{{ allsets.email.title }}</a></li>
+ <li><a href="{% url admin_set allsets.paths.name %}">{{ allsets.paths.title }}</a></li>
+ <li><a href="{% url admin_set allsets.extkeys.name %}">{{ allsets.extkeys.title }}</a></li>
+ </ul>
+ </div>
+ <div id="workflow-sets-menu" class="module">
+ <h2>{% trans "Workflow settings" %}</h2>
+ <ul>
+ <li><a href="{% url admin_set allsets.repgain.name %}">{{ allsets.repgain.title }}</a></li>
+ <li><a href="{% url admin_set allsets.minrep.name %}">{{ allsets.minrep.title }}</a></li>
+ <li><a href="{% url admin_set allsets.voting.name %}">{{ allsets.voting.title }}</a></li>
+ <li><a href="{% url admin_set allsets.badges.name %}">{{ allsets.badges.title }}</a></li>
+ </ul>
+ </div>
+ <div id="forum-sets-menu" class="module">
+ <h2>{% trans "Forum settings" %}</h2>
+ <ul>
+ <li><a href="{% url admin_set allsets.form.name %}">{{ allsets.form.title }}</a></li>
+ <li><a href="{% url admin_set allsets.moderation.name %}">{{ allsets.moderation.title }}</a></li>
+ </ul>
+ </div>
+ <div id="pages-sets-menu" class="module">
+ <h2>{% trans "Static content" %}</h2>
+ <ul>
+ <li><a href="{% url admin_set allsets.about.name %}">{{ allsets.about.title }}</a></li>
+ <li><a href="{% url admin_set allsets.faq.name %}">{{ allsets.faq.title }}</a></li>
+ <li><a href="{% url admin_set allsets.sidebar.name %}">{{ allsets.sidebar.title }}</a></li>
+ </ul>
+ </div>
+ <div id="other-sets-menu" class="module">
+ <h2>{% trans "Other settings" %}</h2>
+ <ul>
+ {% for set in othersets %}
+ <li><a href="{% url admin_set set.name %}">{{ set.title }}</a></li>
+ {% endfor %}
+ </ul>
+ </div>
+ {% comment %}<div id="tools-menu" class="module">
+ <h2>{% trans "Tools" %}</h2>
+ <ul>
+ <li><a href="{% url admin_statistics %}">{% trans "Statistics" %}</a></li>
+ </ul>
+ </div>{% endcomment %}
+ </div>
+ </div>
+ <div id="footer" class="breadcumbs">
+ <a href="http://www.osqa.net">OSQA</a> <span class="version">{{ settings.OSQA_VERSION }}</span>
+ </div>
+ </div>
+</body>
\ No newline at end of file
--- /dev/null
+<div class="module" style="width:385px; height:300px; display: inline-block;">
+ <table style="width: 100%; height: 100%">
+ <caption>{{ graph.caption }}</caption>
+ <tr>
+ <td>
+ <div style="width: 100%; height: 100%" id="{{ graph.id }}"></div>
+ </td>
+ </tr>
+ </table>
+</div>
\ No newline at end of file
-{% extends "osqaadmin/base.html" %}\r
+{% extends basetemplate %}\r
\r
{% load i18n %}\r
{% load extra_tags %}\r
\r
-{% block title %}{{ form.set.title }}{% endblock %}\r
{% block subtitle %}{{ form.set.title }}{% endblock %}\r
+{% block pagename %}{{ form.set.title }}{% endblock %}\r
{% block description %}{{ form.set.description }}{% endblock %}\r
\r
{% block admincontent %}\r
<form action="" method="POST" enctype="multipart/form-data" accept-charset="utf-8">\r
- <table id="admin_form">\r
+ <table id="admin_form" style="width: 100%">\r
{{ form.as_table }}\r
<tr>\r
<th></th>\r
--- /dev/null
+{% extends basetemplate %}
+
+{% load i18n %}
+{% load extra_tags %}
+
+{% block adminjs %}
+<script type='text/javascript' src='{% media "/media/js/excanvas.min.js" %}'></script>
+<script type='text/javascript' src='{% media "/media/js/jquery.flot.min.js" %}'></script>
+<script type="text/javascript">
+ $().ready(function(){
+ {% for graph in graphs %}
+ var {{ graph.id }} = {{ graph.data }};
+ var {{ graph.id }}_placeholder = $('#{{ graph.id }}');
+
+ $.plot({{ graph.id }}_placeholder, [{{ graph.id }}], {
+ xaxis: { mode: "time" },
+ points: { show: false },
+ lines: { show: true }
+ });
+ {% endfor %}
+ });
+</script>
+{% endblock %}
+
+{% block subtitle %}{% trans "Dashboard" %}{% endblock %}
+{% block pagename %}{% trans "Dashboard" %}{% endblock %}
+{% block description %}{% trans "Some simple graphics to help you get a notion of whats going on in your site." %}{% endblock %}
+
+{% block admincontent %}
+{% for graph in graphs %}
+ {% include "osqaadmin/graph.html" %}
+{% endfor %}
+{% endblock %}
\ No newline at end of file
{% endifequal %}
</div>
<div style="float:left;overflow:hidden;width:750px">
- <span class="{{ vote.node.node_type }}-title-link"><a href="{{ vote.node.leaf.get_absolute_url }}">{{ vote.node.leaf.headline }}</a></span>
+ <span class="{{ vote.node.node_type }}-title-link"><a href="{{ vote.node.get_absolute_url }}">{{ vote.node.headline }}</a></span>
<div style="height:5px"></div>
</div>
</div>
#place general question item in the end of other operations
url(r'^%s(?P<id>\d+)/(?P<slug>[\w-]*)$' % _('question/'), app.readers.question, name='question'),
url(r'^%s$' % _('tags/'), app.readers.tags, name='tags'),
- url(r'^%s(?P<tag>[^/]+)/$' % _('tags/'), app.readers.tag, name='tag_questions'),
+ url(r'^%s(?P<tag>.*)/$' % _('tags/'), app.readers.tag, name='tag_questions'),
url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('interesting/')), app.commands.mark_tag, \
kwargs={'reason':'good','action':'add'}, \
url(r'^%s%s%s$' % (_('account/'), _('providers/'), _('add/')), app.auth.signin_page, name='user_add_external_provider'),
- url(r'^%s$' % _('admin/'), app.admin.index, name="admin_index"),
+ url(r'^%s$' % _('admin/'), app.admin.dashboard, name="admin_index"),
+ url(r'^%s%s$' % (_('admin/'), _('switch_interface/')), app.admin.interface_switch, name="admin_switch_interface"),
+ url(r'^%s%s$' % (_('admin/'), _('statistics/')), app.admin.statistics, name="admin_statistics"),
url(r'^%s%s$' % (_('admin/'), _('denormalize/')), app.admin.recalculate_denormalized, name="admin_denormalize"),
url(r'^%s%s$' % (_('admin/'), _('go_bootstrap/')), app.admin.go_bootstrap, name="admin_go_bootstrap"),
url(r'^%s%s$' % (_('admin/'), _('go_defaults/')), app.admin.go_defaults, name="admin_go_defaults"),
from datetime import datetime, timedelta
+import time
from django.shortcuts import render_to_response
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
from django.template import RequestContext
from django.utils.translation import ugettext as _
+from django.utils import simplejson
from django.db.models import Sum
from forum.settings.base import Setting
if request.user.is_authenticated() and request.user.is_superuser:
return fn(request, *args, **kwargs)
else:
- raise Http404
+ return HttpResponseForbidden()
return wrapper
-@super_user_required
-def index(request):
- return render_to_response('osqaadmin/index.html', {
- 'sets': get_all_sets(),
+def admin_page(fn):
+ @super_user_required
+ def wrapper(request, *args, **kwargs):
+ res = fn(request, *args, **kwargs)
+ if isinstance(res, tuple):
+ template, context = res
+ context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
+ context['allsets'] = Setting.sets
+ context['othersets'] = sorted(
+ [s for s in Setting.sets.values() if not s.name in
+ ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'badges', 'about', 'faq', 'sidebar',
+ 'form', 'moderation')]
+ , lambda s1, s2: s1.weight - s2.weight)
+ return render_to_response(template, context, context_instance=RequestContext(request))
+ else:
+ return res
+
+ return wrapper
+
+@admin_page
+def dashboard(request):
+ return ('osqaadmin/dashboard.html', {
'settings_pack': unicode(settings.SETTINGS_PACK),
'statistics': get_statistics(),
'recent_activity': get_recent_activity(),
- }, context_instance=RequestContext(request))
+ })
+
+@super_user_required
+def interface_switch(request):
+ if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
+ settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
-@super_user_required
+ return HttpResponseRedirect(reverse('admin_index'))
+
+@admin_page
+def statistics(request):
+ today = datetime.now()
+ last_month = today - timedelta(days=30)
+
+ last_month_questions = Question.objects.filter(deleted=None, added_at__gt=last_month
+ ).order_by('added_at').values_list('added_at', flat=True)
+
+ last_month_n_questions = Question.objects.filter(deleted=None, added_at__lt=last_month).count()
+ qgraph_data = simplejson.dumps([
+ (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
+ for i, d in enumerate(last_month_questions)
+ ])
+
+ last_month_users = User.objects.filter(date_joined__gt=last_month
+ ).order_by('date_joined').values_list('date_joined', flat=True)
+
+ last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
+
+ ugraph_data = simplejson.dumps([
+ (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
+ for i, d in enumerate(last_month_users)
+ ])
+
+ return 'osqaadmin/statistics.html', {
+ 'graphs': [
+ {
+ 'id': 'questions_graph',
+ 'caption': _("Questions Graph"),
+ 'data': qgraph_data
+ },{
+ 'id': 'userss_graph',
+ 'caption': _("Users Graph"),
+ 'data': ugraph_data
+ }
+ ]
+ }
+
+
+@admin_page
def settings_set(request, set_name):
set = Setting.sets.get(set_name, None)
else:
form = SettingsSetForm(set)
- return render_to_response('osqaadmin/set.html', {
+ return 'osqaadmin/set.html', {
'form': form,
'markdown': set.markdown,
- 'sets': get_all_sets(),
- }, context_instance=RequestContext(request))
+ }
-def get_all_sets():
- return sorted(Setting.sets.values(), lambda s1, s2: s1.weight - s2.weight)
def get_recent_activity():
return Action.objects.order_by('-action_date')[0:30]
'answers_last_24': Answer.objects.filter(deleted=None, added_at__gt=(datetime.now() - timedelta(days=1))).count(),
}
-@super_user_required
+@super_user_required
def go_bootstrap(request):
#todo: this is the quick and dirty way of implementing a bootstrap mode
try:
--- /dev/null
+Just an empty placeholder.
\ No newline at end of file