summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rwxr-xr-x.idea/compiler.xml24
-rwxr-xr-x.idea/copyright/profiles_settings.xml5
-rwxr-xr-x.idea/encodings.xml5
-rwxr-xr-x.idea/misc.xml127
-rwxr-xr-x.idea/modules.xml9
-rwxr-xr-x.idea/uiDesigner.xml125
-rwxr-xr-x.idea/vcs.xml7
-rwxr-xr-x.idea/workspace.xml687
-rw-r--r--PENDING1
-rw-r--r--development.log72
-rw-r--r--django_authopenid/forms.py4
-rw-r--r--django_authopenid/util.py2
-rwxr-xr-xdjango_authopenid/views.py2
-rw-r--r--forum/__init__.py2
-rw-r--r--forum/admin.py11
-rw-r--r--forum/auth.py4
-rw-r--r--forum/forms.py3
-rw-r--r--forum/management/__init__.py3
-rw-r--r--forum/management/commands/send_email_alerts.py2
-rw-r--r--forum/managers.py247
-rw-r--r--forum/middleware/__init__.py (renamed from middleware/__init__.py)0
-rw-r--r--forum/middleware/anon_user.py (renamed from middleware/anon_user.py)4
-rw-r--r--forum/middleware/cancel.py (renamed from middleware/cancel.py)2
-rw-r--r--forum/middleware/pagesize.py (renamed from middleware/pagesize.py)0
-rw-r--r--forum/models.py955
-rwxr-xr-xforum/models/__init__.py341
-rwxr-xr-xforum/models/answer.py133
-rwxr-xr-xforum/models/base.py139
-rwxr-xr-xforum/models/meta.py89
-rwxr-xr-xforum/models/question.py335
-rwxr-xr-xforum/models/repute.py109
-rwxr-xr-xforum/models/tag.py85
-rwxr-xr-xforum/models/user.py67
-rwxr-xr-xforum/modules.py54
-rw-r--r--forum/skins/README13
-rw-r--r--forum/skins/default/media/js/com.cnprog.admin.js4
-rw-r--r--forum/skins/default/media/js/com.cnprog.i18n.js4
-rw-r--r--forum/skins/default/media/js/com.cnprog.utils.js2
-rw-r--r--forum/skins/default/media/js/wmd/wmd.js56
-rw-r--r--forum/skins/default/media/style/style.css2837
-rw-r--r--forum/skins/default/templates/ask.html7
-rw-r--r--forum/skins/default/templates/authopenid/complete.html1
-rw-r--r--forum/skins/default/templates/authopenid/confirm_email.txt14
-rw-r--r--forum/skins/default/templates/authopenid/email_validation.txt16
-rw-r--r--forum/skins/default/templates/authopenid/sendpw_email.txt4
-rwxr-xr-xforum/skins/default/templates/base.html7
-rw-r--r--forum/skins/default/templates/header.html1
-rwxr-xr-x[-rw-r--r--]forum/skins/default/templates/index.html289
-rwxr-xr-xforum/skins/default/templates/index_.html124
-rw-r--r--forum/skins/default/templates/question.html521
-rw-r--r--forum/skins/default/templates/question_edit.html6
-rw-r--r--forum/skins/default/templates/questions.html275
-rw-r--r--forum/skins/default/templates/user_email_subscriptions.html3
-rw-r--r--forum/skins/default/templates/user_info.html2
-rw-r--r--forum/templatetags/extra_filters.py11
-rw-r--r--forum/urls.py83
-rw-r--r--forum/user_messages/__init__.py (renamed from user_messages/__init__.py)0
-rw-r--r--forum/user_messages/context_processors.py (renamed from user_messages/context_processors.py)2
-rw-r--r--forum/utils/__init__.py (renamed from pgfulltext/__init__.py)0
-rw-r--r--forum/utils/cache.py (renamed from utils/cache.py)0
-rw-r--r--forum/utils/decorators.py (renamed from utils/decorators.py)0
-rw-r--r--forum/utils/diff.py (renamed from forum/diff.py)0
-rw-r--r--forum/utils/forms.py (renamed from utils/forms.py)0
-rw-r--r--forum/utils/html.py (renamed from utils/html.py)0
-rw-r--r--forum/utils/lists.py (renamed from utils/lists.py)0
-rw-r--r--forum/utils/odict.py (renamed from utils/odict.py)0
-rw-r--r--forum/views.py2410
-rw-r--r--forum/views/README12
-rw-r--r--forum/views/__init__.py5
-rw-r--r--forum/views/commands.py335
-rw-r--r--forum/views/content.py1394
-rw-r--r--forum/views/meta.py15
-rw-r--r--forum/views/readers.py588
-rw-r--r--forum/views/users.py7
-rw-r--r--forum/views/writers.py442
-rwxr-xr-x[-rw-r--r--]forum_modules/__init__.py (renamed from utils/__init__.py)0
-rwxr-xr-xforum_modules/books/__init__.py3
-rwxr-xr-xforum_modules/books/models.py63
-rwxr-xr-xforum_modules/books/urls.py10
-rwxr-xr-x[-rw-r--r--]forum_modules/books/views.py (renamed from forum/views/books.py)31
-rwxr-xr-xforum_modules/pgfulltext/__init__.py9
-rwxr-xr-xforum_modules/pgfulltext/handlers.py11
-rwxr-xr-x[-rw-r--r--]forum_modules/pgfulltext/management.py (renamed from pgfulltext/management.py)18
-rwxr-xr-xforum_modules/pgfulltext/pg_fts_install.sql38
-rwxr-xr-xforum_modules/sphinxfulltext/DISABLED0
-rwxr-xr-xforum_modules/sphinxfulltext/__init__.py0
-rwxr-xr-xforum_modules/sphinxfulltext/dependencies.py2
-rwxr-xr-xforum_modules/sphinxfulltext/handlers.py4
-rwxr-xr-xforum_modules/sphinxfulltext/models.py10
-rwxr-xr-xforum_modules/sphinxfulltext/settings.py5
-rw-r--r--junk.py3
-rw-r--r--locale/en/LC_MESSAGES/django.mobin367 -> 26986 bytes
-rw-r--r--locale/en/LC_MESSAGES/django.po2240
-rwxr-xr-xosqa.iml9
-rwxr-xr-xrmpyc1
-rw-r--r--session_messages/.svn/all-wcprops23
-rw-r--r--session_messages/.svn/dir-prop-base6
-rw-r--r--session_messages/.svn/entries64
-rw-r--r--session_messages/.svn/format1
-rw-r--r--session_messages/.svn/text-base/__init__.py.svn-base36
-rw-r--r--session_messages/.svn/text-base/context_processors.py.svn-base48
-rw-r--r--session_messages/.svn/text-base/models.py.svn-base3
-rwxr-xr-xsettings.py11
-rwxr-xr-xsettings_local.py.dist47
-rw-r--r--sql_scripts/update_2010_02_22.sql1
-rw-r--r--user_messages/models.py3
107 files changed, 7391 insertions, 8456 deletions
diff --git a/.gitignore b/.gitignore
index c02c6f1a..11c8905f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@
osqa.wsgi
nbproject
settings_local.py
+.idea
+*.iml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100755
index 00000000..b9a1798a
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <option name="DEFAULT_COMPILER" value="Javac" />
+ <resourceExtensions>
+ <entry name=".+\.(properties|xml|html|dtd|tld)" />
+ <entry name=".+\.(gif|png|jpeg|jpg)" />
+ </resourceExtensions>
+ <wildcardResourcePatterns>
+ <entry name="?*.properties" />
+ <entry name="?*.xml" />
+ <entry name="?*.gif" />
+ <entry name="?*.png" />
+ <entry name="?*.jpeg" />
+ <entry name="?*.jpg" />
+ <entry name="?*.html" />
+ <entry name="?*.dtd" />
+ <entry name="?*.tld" />
+ <entry name="?*.ftl" />
+ </wildcardResourcePatterns>
+ <annotationProcessing enabled="false" useClasspath="true" />
+ </component>
+</project>
+
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100755
index 00000000..b385f01f
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,5 @@
+<component name="CopyrightManager">
+ <settings default="">
+ <module2copyright />
+ </settings>
+</component> \ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100755
index 00000000..7c62b52a
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
+</project>
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100755
index 00000000..5253b461
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="AnalysisProjectProfileManager">
+ <option name="PROJECT_PROFILE" />
+ <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+ <list size="0" />
+ </component>
+ <component name="Archetypes.Configuration">
+ <option name="archetypesFileLocation" value="archetypes.xml" />
+ <option name="workingDirectory" value="" />
+ </component>
+ <component name="DependencyValidationManager">
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+ </component>
+ <component name="HandyEdit.TapestrySupport" tapestryProject="false" askEnableTapestry="true" componentSpec="true">
+ <locations pageRoot="" componentRoot="" applicationFile="" pagePropertiesRoot="" />
+ <packages page="" component="" />
+ <completion properties="true" methods="false" htmlAttributes="true" />
+ <toolWindows htmlAttributes="true" />
+ <ext extensionComponentSpec="jwc" extensionPageSpec="page" extensionScript="script" />
+ <create />
+ </component>
+ <component name="IdProvider" IDEtalkID="119765BF5908F4A0676504FC73A9C8F4" />
+ <component name="JavadocGenerationManager">
+ <option name="OUTPUT_DIRECTORY" />
+ <option name="OPTION_SCOPE" value="protected" />
+ <option name="OPTION_HIERARCHY" value="true" />
+ <option name="OPTION_NAVIGATOR" value="true" />
+ <option name="OPTION_INDEX" value="true" />
+ <option name="OPTION_SEPARATE_INDEX" value="true" />
+ <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+ <option name="OPTION_DEPRECATED_LIST" value="true" />
+ <option name="OTHER_OPTIONS" value="" />
+ <option name="HEAP_SIZE" />
+ <option name="LOCALE" />
+ <option name="OPEN_IN_BROWSER" value="true" />
+ </component>
+ <component name="ProjectDetails">
+ <option name="projectName" value="osqa" />
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/out" />
+ </component>
+ <component name="SvnBranchConfigurationManager">
+ <option name="mySupportsUserInfoFilter" value="true" />
+ </component>
+ <component name="VssConfiguration">
+ <option name="CLIENT_PATH" value="" />
+ <option name="SRCSAFEINI_PATH" value="" />
+ <option name="USER_NAME" value="" />
+ <option name="PWD" value="" />
+ <CheckoutOptions>
+ <option name="COMMENT" value="" />
+ <option name="DO_NOT_GET_LATEST_VERSION" value="false" />
+ <option name="REPLACE_WRITABLE" value="false" />
+ <option name="RECURSIVE" value="false" />
+ </CheckoutOptions>
+ <CheckinOptions>
+ <option name="COMMENT" value="" />
+ <option name="KEEP_CHECKED_OUT" value="false" />
+ <option name="RECURSIVE" value="false" />
+ </CheckinOptions>
+ <AddOptions>
+ <option name="STORE_ONLY_LATEST_VERSION" value="false" />
+ <option name="CHECK_OUT_IMMEDIATELY" value="false" />
+ </AddOptions>
+ <UndocheckoutOptions>
+ <option name="MAKE_WRITABLE" value="false" />
+ <option name="REPLACE_LOCAL_COPY" value="2" />
+ <option name="RECURSIVE" value="false" />
+ </UndocheckoutOptions>
+ <GetOptions>
+ <option name="REPLACE_WRITABLE" value="0" />
+ <option name="MAKE_WRITABLE" value="false" />
+ <option name="ANSWER_NEGATIVELY" value="false" />
+ <option name="ANSWER_POSITIVELY" value="false" />
+ <option name="RECURSIVE" value="false" />
+ <option name="VERSION" />
+ </GetOptions>
+ </component>
+ <component name="WebServicesPlugin" addRequiredLibraries="true" />
+ <component name="masterDetails">
+ <states>
+ <state key="ArtifactsStructureConfigurable.UI">
+ <UIState>
+ <splitter-proportions>
+ <SplitterProportionsDataImpl />
+ </splitter-proportions>
+ <settings />
+ </UIState>
+ </state>
+ <state key="Copyright.UI">
+ <UIState>
+ <splitter-proportions>
+ <SplitterProportionsDataImpl />
+ </splitter-proportions>
+ </UIState>
+ </state>
+ <state key="ProjectJDKs.UI">
+ <UIState>
+ <splitter-proportions>
+ <SplitterProportionsDataImpl>
+ <option name="proportions">
+ <list>
+ <option value="0.2" />
+ </list>
+ </option>
+ </SplitterProportionsDataImpl>
+ </splitter-proportions>
+ <last-edited>1.6</last-edited>
+ </UIState>
+ </state>
+ <state key="ScopeChooserConfigurable.UI">
+ <UIState>
+ <splitter-proportions>
+ <SplitterProportionsDataImpl />
+ </splitter-proportions>
+ <settings />
+ </UIState>
+ </state>
+ </states>
+ </component>
+</project>
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100755
index 00000000..9a64d92a
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/osqa.iml" filepath="$PROJECT_DIR$/osqa.iml" />
+ </modules>
+ </component>
+</project>
+
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100755
index 00000000..1e7cce4b
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Palette2">
+ <group name="Swing">
+ <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+ </item>
+ <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+ <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+ <initial-values>
+ <property name="text" value="Button" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="RadioButton" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="CheckBox" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="Label" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+ <preferred-size width="-1" height="20" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+ </item>
+ </group>
+ </component>
+</project>
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100755
index 00000000..7e76f0fd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project>
+
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100755
index 00000000..c22d0818
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,687 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CCaseConfig">
+ <option name="checkoutReserved" value="false" />
+ <option name="markExternalChangeAsUpToDate" value="true" />
+ <option name="checkInUseHijack" value="true" />
+ <option name="useUcmModel" value="true" />
+ <option name="isOffline" value="false" />
+ <option name="synchOutside" value="false" />
+ <option name="isHistoryResticted" value="true" />
+ <option name="useIdenticalSwitch" value="true" />
+ <option name="synchActivitiesOnRefresh" value="true" />
+ <option name="lastScr" value="" />
+ <option name="scrTextFileName" value="" />
+ <option name="historyRevisionsNumber" value="4" />
+ </component>
+ <component name="ChangeListManager" verified="true">
+ <list default="true" readonly="true" id="df1b69cf-9c35-4a78-9890-c59b542399e8" name="Default" comment="">
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/views.py" afterPath="$PROJECT_DIR$/fbconnect/views.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/uiDesigner.xml" afterPath="$PROJECT_DIR$/.idea/uiDesigner.xml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/django_authopenid/urls.py" afterPath="$PROJECT_DIR$/django_authopenid/urls.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/settings.py" afterPath="$PROJECT_DIR$/settings.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/tag.py" afterPath="$PROJECT_DIR$/forum/models/tag.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/tests.py" afterPath="$PROJECT_DIR$/fbconnect/tests.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/WISH_LIST" afterPath="$PROJECT_DIR$/WISH_LIST" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/middleware/anon_user.py" afterPath="$PROJECT_DIR$/forum/middleware/anon_user.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/urls.py" afterPath="$PROJECT_DIR$/fbconnect/urls.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/question.py" afterPath="$PROJECT_DIR$/forum/models/question.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/meta.py" afterPath="$PROJECT_DIR$/forum/models/meta.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/modules.xml" afterPath="$PROJECT_DIR$/.idea/modules.xml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/copyright/profiles_settings.xml" afterPath="$PROJECT_DIR$/.idea/copyright/profiles_settings.xml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/__init__.py" afterPath="$PROJECT_DIR$/forum/models/__init__.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/answer.py" afterPath="$PROJECT_DIR$/forum/models/answer.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/rmpyc" afterPath="$PROJECT_DIR$/rmpyc" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/user.py" afterPath="$PROJECT_DIR$/forum/models/user.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/vcs.xml" afterPath="$PROJECT_DIR$/.idea/vcs.xml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/__init__.py" afterPath="$PROJECT_DIR$/fbconnect/__init__.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/base.py" afterPath="$PROJECT_DIR$/forum/models/base.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/django_authopenid/views.py" afterPath="$PROJECT_DIR$/django_authopenid/views.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/__init__.py" afterPath="$PROJECT_DIR$/forum/__init__.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/repute.py" afterPath="$PROJECT_DIR$/forum/models/repute.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/base.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/base.html" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/authopenid/signin.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/authopenid/signin.html" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_edit.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_edit.html" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/models.py" afterPath="$PROJECT_DIR$/fbconnect/models.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/settings_local.py.dist" afterPath="$PROJECT_DIR$/settings_local.py.dist" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/fb.py" afterPath="$PROJECT_DIR$/fbconnect/fb.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/osqa.iml" afterPath="$PROJECT_DIR$/osqa.iml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/tester/models.py" afterPath="$PROJECT_DIR$/tester/models.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/fbconnect/xd_receiver.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/fbconnect/xd_receiver.html" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/misc.xml" afterPath="$PROJECT_DIR$/.idea/misc.xml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/question.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/question.html" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/questions.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/questions.html" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2010_01_23.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2010_01_23.sql" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/readers.py" afterPath="$PROJECT_DIR$/forum/views/readers.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/compiler.xml" afterPath="$PROJECT_DIR$/.idea/compiler.xml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/forms.py" afterPath="$PROJECT_DIR$/fbconnect/forms.py" />
+ <change type="DELETED" beforePath="C:\osqadev\updated\osqa\user_messages\models.py" afterPath="" />
+ <change type="MOVED" beforePath="C:\osqadev\updated\osqa\forum\diff.py" afterPath="$PROJECT_DIR$/forum/utils/diff.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/pjson.py" afterPath="$PROJECT_DIR$/fbconnect/pjson.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/encodings.xml" afterPath="$PROJECT_DIR$/.idea/encodings.xml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/tester/__init__.py" afterPath="$PROJECT_DIR$/tester/__init__.py" />
+ </list>
+ <ignored path="osqa.iws" />
+ <ignored path=".idea/workspace.xml" />
+ <option name="TRACKING_ENABLED" value="true" />
+ <option name="SHOW_DIALOG" value="false" />
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+ <option name="LAST_RESOLUTION" value="IGNORE" />
+ </component>
+ <component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
+ <component name="CreatePatchCommitExecutor">
+ <option name="PATCH_PATH" value="" />
+ <option name="REVERSE_PATCH" value="false" />
+ </component>
+ <component name="DaemonCodeAnalyzer">
+ <disable_hints />
+ </component>
+ <component name="DebuggerManager">
+ <breakpoint_any>
+ <breakpoint>
+ <option name="NOTIFY_CAUGHT" value="true" />
+ <option name="NOTIFY_UNCAUGHT" value="true" />
+ <option name="ENABLED" value="false" />
+ <option name="LOG_ENABLED" value="false" />
+ <option name="LOG_EXPRESSION_ENABLED" value="false" />
+ <option name="SUSPEND_POLICY" value="SuspendAll" />
+ <option name="COUNT_FILTER_ENABLED" value="false" />
+ <option name="COUNT_FILTER" value="0" />
+ <option name="CONDITION_ENABLED" value="false" />
+ <option name="CLASS_FILTERS_ENABLED" value="false" />
+ <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+ <option name="CONDITION" value="" />
+ <option name="LOG_MESSAGE" value="" />
+ </breakpoint>
+ <breakpoint>
+ <option name="NOTIFY_CAUGHT" value="true" />
+ <option name="NOTIFY_UNCAUGHT" value="true" />
+ <option name="ENABLED" value="false" />
+ <option name="LOG_ENABLED" value="false" />
+ <option name="LOG_EXPRESSION_ENABLED" value="false" />
+ <option name="SUSPEND_POLICY" value="SuspendAll" />
+ <option name="COUNT_FILTER_ENABLED" value="false" />
+ <option name="COUNT_FILTER" value="0" />
+ <option name="CONDITION_ENABLED" value="false" />
+ <option name="CLASS_FILTERS_ENABLED" value="false" />
+ <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+ <option name="CONDITION" value="" />
+ <option name="LOG_MESSAGE" value="" />
+ </breakpoint>
+ </breakpoint_any>
+ <breakpoint_rules />
+ <ui_properties />
+ </component>
+ <component name="FavoritesManager">
+ <favorites_list name="osqa" />
+ </component>
+ <component name="FileColors" enabled="true" enabledForTabs="true" />
+ <component name="FileEditorManager">
+ <leaf>
+ <file leaf-file-name="settings.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/settings.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="71" column="20" selection-start="2573" selection-end="2573" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="anon_user.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/middleware/anon_user.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="3" column="35" selection-start="174" selection-end="174" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="cancel.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/middleware/cancel.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="pagesize.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/middleware/pagesize.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="__init__.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/middleware/__init__.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="__init__.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/user_messages/__init__.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="context_processors.py" pinned="false" current="true" current-in-tab="true">
+ <entry file="file://$PROJECT_DIR$/forum/user_messages/context_processors.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="14" column="27" selection-start="330" selection-end="330" vertical-scroll-proportion="0.3707165">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="__init__.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/__init__.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="34" selection-start="34" selection-end="34" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="readers.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/views/readers.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="23" column="38" selection-start="913" selection-end="913" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ </leaf>
+ </component>
+ <component name="FindManager">
+ <FindUsagesManager>
+ <setting name="OPEN_NEW_TAB" value="false" />
+ </FindUsagesManager>
+ </component>
+ <component name="IdeDocumentHistory">
+ <option name="changedFiles">
+ <list>
+ <option value="$PROJECT_DIR$/settings_local.py" />
+ <option value="$PROJECT_DIR$/settings_local.py.dist" />
+ <option value="$PROJECT_DIR$/forum/models/answer.py" />
+ <option value="$PROJECT_DIR$/forum/models/question.py" />
+ <option value="$PROJECT_DIR$/django_authopenid/forms.py" />
+ <option value="$PROJECT_DIR$/forum/models/base.py" />
+ <option value="$PROJECT_DIR$/forum/views/writers.py" />
+ <option value="$PROJECT_DIR$/forum/views/commands.py" />
+ <option value="$PROJECT_DIR$/forum/views/users.py" />
+ <option value="$PROJECT_DIR$/forum/views/meta.py" />
+ <option value="$PROJECT_DIR$/forum/views/books.py" />
+ <option value="$PROJECT_DIR$/forum/views/readers.py" />
+ <option value="$PROJECT_DIR$/forum/__init__.py" />
+ <option value="$PROJECT_DIR$/settings.py" />
+ <option value="$PROJECT_DIR$/forum/middleware/anon_user.py" />
+ <option value="$PROJECT_DIR$/forum/user_messages/context_processors.py" />
+ </list>
+ </option>
+ </component>
+ <component name="ModuleEditorState">
+ <option name="LAST_EDITED_MODULE_NAME" />
+ <option name="LAST_EDITED_TAB_NAME" />
+ </component>
+ <component name="ProjectInspectionProfilesVisibleTreeState">
+ <entry key="Project Default">
+ <profile-state />
+ </entry>
+ </component>
+ <component name="ProjectLevelVcsManager">
+ <OptionsSetting value="true" id="Add" />
+ <OptionsSetting value="true" id="Remove" />
+ <OptionsSetting value="true" id="Checkout" />
+ <OptionsSetting value="true" id="Update" />
+ <OptionsSetting value="true" id="Status" />
+ <OptionsSetting value="true" id="Edit" />
+ <OptionsSetting value="true" id="Undo Check Out" />
+ <OptionsSetting value="true" id="Get Latest Version" />
+ <ConfirmationsSetting value="1" id="Add" />
+ <ConfirmationsSetting value="0" id="Remove" />
+ </component>
+ <component name="ProjectReloadState">
+ <option name="STATE" value="0" />
+ </component>
+ <component name="ProjectView">
+ <navigator currentView="ProjectPane" proportions="" version="1" splitterProportion="0.5">
+ <flattenPackages />
+ <showMembers />
+ <showModules />
+ <showLibraryContents />
+ <hideEmptyPackages />
+ <abbreviatePackageNames />
+ <autoscrollToSource />
+ <autoscrollFromSource />
+ <sortByType />
+ </navigator>
+ <panes>
+ <pane id="PackagesPane" />
+ <pane id="Favorites" />
+ <pane id="ProjectPane">
+ <subPane>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="osqa" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="osqa" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="osqa" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ </subPane>
+ </pane>
+ <pane id="Scope" />
+ </panes>
+ </component>
+ <component name="PropertiesComponent">
+ <property name="project.structure.last.edited" value="Libraries" />
+ <property name="GoToFile.includeJavaFiles" value="false" />
+ <property name="project.structure.proportion" value="0.0" />
+ <property name="options.splitter.main.proportions" value="0.3" />
+ <property name="MemberChooser.sorted" value="false" />
+ <property name="options.lastSelected" value="preferences.editor" />
+ <property name="project.structure.side.proportion" value="0.2" />
+ <property name="MemberChooser.copyJavadoc" value="false" />
+ <property name="GoToClass.toSaveIncludeLibraries" value="false" />
+ <property name="WebServerToolWindowFactoryState" value="false" />
+ <property name="MemberChooser.showClasses" value="true" />
+ <property name="GoToClass.includeLibraries" value="false" />
+ <property name="options.splitter.details.proportions" value="0.2" />
+ <property name="options.searchVisible" value="true" />
+ </component>
+ <component name="Regex">
+ <option name="pos1" value="218" />
+ <option name="pos2" value="218" />
+ <option name="pos3" value="162" />
+ <option name="pos4" value="444" />
+ <option name="pos5" value="162" />
+ <option name="autoUpdate" value="true" />
+ <option name="referenceOn" value="false" />
+ <option name="referencePos" value="0" />
+ <option name="showLabels" value="true" />
+ </component>
+ <component name="RunManager">
+ <configuration default="true" type="PythonUnitTestConfigurationType" factoryName="Python's unittest">
+ <option name="INTERPRETER_OPTIONS" value="" />
+ <option name="PARENT_ENVS" value="true" />
+ <option name="SDK_HOME" value="" />
+ <option name="WORKING_DIRECTORY" value="" />
+ <option name="IS_MODULE_SDK" value="false" />
+ <envs />
+ <module name="osqa" />
+ <option name="SCRIPT_NAME" value="" />
+ <option name="CLASS_NAME" value="" />
+ <option name="METHOD_NAME" value="" />
+ <option name="FOLDER_NAME" value="" />
+ <option name="TEST_TYPE" value="TEST_SCRIPT" />
+ <method>
+ <option name="AntTarget" enabled="false" />
+ <option name="BuildArtifacts" enabled="false" />
+ <option name="Make" enabled="true" />
+ <option name="Maven.BeforeRunTask" enabled="false" />
+ </method>
+ </configuration>
+ <configuration default="true" type="PythonConfigurationType" factoryName="Python">
+ <option name="INTERPRETER_OPTIONS" value="" />
+ <option name="PARENT_ENVS" value="true" />
+ <option name="SDK_HOME" value="" />
+ <option name="WORKING_DIRECTORY" value="" />
+ <option name="IS_MODULE_SDK" value="false" />
+ <envs>
+ <env name="PYTHONUNBUFFERED" value="1" />
+ </envs>
+ <module name="osqa" />
+ <option name="SCRIPT_NAME" value="" />
+ <option name="PARAMETERS" value="" />
+ <method>
+ <option name="AntTarget" enabled="false" />
+ <option name="BuildArtifacts" enabled="false" />
+ <option name="Make" enabled="true" />
+ <option name="Maven.BeforeRunTask" enabled="false" />
+ </method>
+ </configuration>
+ <configuration default="true" type="Remote" factoryName="Remote">
+ <option name="USE_SOCKET_TRANSPORT" value="true" />
+ <option name="SERVER_MODE" value="false" />
+ <option name="SHMEM_ADDRESS" value="javadebug" />
+ <option name="HOST" value="localhost" />
+ <option name="PORT" value="5005" />
+ <method>
+ <option name="AntTarget" enabled="false" />
+ <option name="BuildArtifacts" enabled="false" />
+ <option name="Maven.BeforeRunTask" enabled="false" />
+ </method>
+ </configuration>
+ <configuration default="true" type="py.test" factoryName="py.test">
+ <option name="INTERPRETER_OPTIONS" value="" />
+ <option name="PARENT_ENVS" value="true" />
+ <option name="SDK_HOME" value="" />
+ <option name="WORKING_DIRECTORY" value="" />
+ <option name="IS_MODULE_SDK" value="false" />
+ <envs />
+ <module name="osqa" />
+ <option name="testToRun" value="" />
+ <option name="keywords" value="" />
+ <method>
+ <option name="AntTarget" enabled="false" />
+ <option name="BuildArtifacts" enabled="false" />
+ <option name="Make" enabled="true" />
+ <option name="Maven.BeforeRunTask" enabled="false" />
+ </method>
+ </configuration>
+ <configuration default="true" type="Applet" factoryName="Applet">
+ <module name="" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="HTML_FILE_NAME" />
+ <option name="HTML_USED" value="false" />
+ <option name="WIDTH" value="400" />
+ <option name="HEIGHT" value="300" />
+ <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" />
+ <option name="VM_PARAMETERS" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <method>
+ <option name="AntTarget" enabled="false" />
+ <option name="BuildArtifacts" enabled="false" />
+ <option name="Make" enabled="true" />
+ <option name="Maven.BeforeRunTask" enabled="false" />
+ </method>
+ </configuration>
+ <configuration default="true" type="Application" factoryName="Application">
+ <extension name="coverage" enabled="false" merge="false" />
+ <extension name="snapshooter" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="VM_PARAMETERS" />
+ <option name="PROGRAM_PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="ENABLE_SWING_INSPECTOR" value="false" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <module name="" />
+ <envs />
+ <method>
+ <option name="AntTarget" enabled="false" />
+ <option name="BuildArtifacts" enabled="false" />
+ <option name="Make" enabled="true" />
+ <option name="Maven.BeforeRunTask" enabled="false" />
+ </method>
+ </configuration>
+ <configuration default="true" type="JUnit" factoryName="JUnit">
+ <extension name="coverage" enabled="false" merge="false" />
+ <extension name="snapshooter" />
+ <module name="" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="PACKAGE_NAME" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="METHOD_NAME" />
+ <option name="TEST_OBJECT" value="class" />
+ <option name="VM_PARAMETERS" />
+ <option name="PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <option name="TEST_SEARCH_SCOPE">
+ <value defaultName="moduleWithDependencies" />
+ </option>
+ <envs />
+ <method>
+ <option name="AntTarget" enabled="false" />
+ <option name="BuildArtifacts" enabled="false" />
+ <option name="Make" enabled="true" />
+ <option name="Maven.BeforeRunTask" enabled="false" />
+ </method>
+ </configuration>
+ <list size="0" />
+ <configuration name="&lt;template&gt;" type="WebApp" default="true" selected="false">
+ <Host>localhost</Host>
+ <Port>5050</Port>
+ </configuration>
+ </component>
+ <component name="ShelveChangesManager" show_recycled="false" />
+ <component name="StarteamConfiguration">
+ <option name="SERVER" value="" />
+ <option name="PORT" value="49201" />
+ <option name="USER" value="" />
+ <option name="PASSWORD" value="" />
+ <option name="PROJECT" value="" />
+ <option name="VIEW" value="" />
+ <option name="ALTERNATIVE_WORKING_PATH" value="" />
+ <option name="LOCK_ON_CHECKOUT" value="false" />
+ <option name="UNLOCK_ON_CHECKIN" value="false" />
+ </component>
+ <component name="SvnConfiguration">
+ <option name="USER" value="" />
+ <option name="PASSWORD" value="" />
+ <option name="LAST_MERGED_REVISION" />
+ <option name="UPDATE_RUN_STATUS" value="false" />
+ <option name="MERGE_DRY_RUN" value="false" />
+ <option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
+ <option name="UPDATE_LOCK_ON_DEMAND" value="false" />
+ <option name="IGNORE_SPACES_IN_MERGE" value="false" />
+ <option name="DETECT_NESTED_COPIES" value="false" />
+ <option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
+ <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
+ <configuration useDefault="true">F:\Users\COOL\AppData\Roaming\Subversion</configuration>
+ <myIsUseDefaultProxy>false</myIsUseDefaultProxy>
+ <supportedVersion>125</supportedVersion>
+ </component>
+ <component name="TaskManager">
+ <task active="true" id="Default" summary="Default task">
+ <created>1266447811437</created>
+ <updated>1266447811437</updated>
+ </task>
+ <servers />
+ </component>
+ <component name="TodoView" selected-index="0">
+ <todo-panel id="selected-file">
+ <are-packages-shown value="false" />
+ <are-modules-shown value="false" />
+ <flatten-packages value="false" />
+ <is-autoscroll-to-source value="true" />
+ </todo-panel>
+ <todo-panel id="all">
+ <are-packages-shown value="true" />
+ <are-modules-shown value="false" />
+ <flatten-packages value="false" />
+ <is-autoscroll-to-source value="true" />
+ </todo-panel>
+ <todo-panel id="default-changelist">
+ <are-packages-shown value="false" />
+ <are-modules-shown value="false" />
+ <flatten-packages value="false" />
+ <is-autoscroll-to-source value="false" />
+ </todo-panel>
+ </component>
+ <component name="ToolWindowManager">
+ <frame x="1277" y="-3" width="1286" height="806" extended-state="6" />
+ <editor active="false" />
+ <layout>
+ <window_info id="Archetypes" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+ <window_info id="Changes" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+ <window_info id="Palette" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+ <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.329927" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+ <window_info id="IDEtalk Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+ <window_info id="IDEtalk" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+ <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+ <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.329927" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
+ <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="true" content_ui="tabs" />
+ <window_info id="Maven Projects" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+ <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Dependency Viewer" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+ <window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.24959481" sideWeight="0.659854" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
+ <window_info id="Regex" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
+ <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+ <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+ <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
+ </layout>
+ </component>
+ <component name="VcsManagerConfiguration">
+ <option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true" />
+ <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true" />
+ <option name="PERFORM_UPDATE_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_COMMIT_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_EDIT_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false" />
+ <option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="true" />
+ <option name="FORCE_NON_EMPTY_COMMENT" value="false" />
+ <option name="LAST_COMMIT_MESSAGE" />
+ <option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true" />
+ <option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false" />
+ <option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false" />
+ <option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false" />
+ <option name="REFORMAT_BEFORE_FILE_COMMIT" value="false" />
+ <option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8" />
+ <option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5" />
+ <option name="ACTIVE_VCS_NAME" />
+ <option name="UPDATE_GROUP_BY_PACKAGES" value="false" />
+ <option name="UPDATE_GROUP_BY_CHANGELIST" value="false" />
+ <option name="SHOW_FILE_HISTORY_AS_TREE" value="false" />
+ <option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6" />
+ </component>
+ <component name="XDebuggerManager">
+ <breakpoint-manager />
+ </component>
+ <component name="editorHistoryManager">
+ <entry file="file://$PROJECT_DIR$/forum/models/answer.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="24" column="54" selection-start="758" selection-end="758" vertical-scroll-proportion="0.1863354">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/views/commands.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="10" column="44" selection-start="413" selection-end="413" vertical-scroll-proportion="0.26397514">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/views/writers.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="12" column="0" selection-start="509" selection-end="553" vertical-scroll-proportion="-8.16">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/views/users.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="9" column="44" selection-start="539" selection-end="539" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/views/meta.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="5" column="44" selection-start="295" selection-end="295" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/views/books.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="23" column="80" selection-start="867" selection-end="867" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/urls.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="-1.666149">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/views/readers.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="23" column="38" selection-start="913" selection-end="913" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/__init__.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="34" selection-start="34" selection-end="34" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/settings.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="71" column="20" selection-start="2573" selection-end="2573" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/middleware/anon_user.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="3" column="35" selection-start="174" selection-end="174" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/middleware/cancel.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/middleware/pagesize.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/middleware/__init__.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/user_messages/__init__.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/user_messages/context_processors.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="14" column="27" selection-start="330" selection-end="330" vertical-scroll-proportion="0.3707165">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </component>
+</project>
+
diff --git a/PENDING b/PENDING
index 0f053357..9d2f00ee 100644
--- a/PENDING
+++ b/PENDING
@@ -4,7 +4,6 @@ new features (go to law school, get a job, do something real)
Just a joke - pick yourself a task and work on it.
==Refactoring==
-* (see note 1) analyze and split /views/content.py - Evgeny
* analyze and split models.py --> models/
* create forum/modules directory
* make modules load into the forum app like they
diff --git a/development.log b/development.log
deleted file mode 100644
index abe1aac0..00000000
--- a/development.log
+++ /dev/null
@@ -1,72 +0,0 @@
-==Aug 5, 2009 Evgeny==
-====Interface changes===
-Merged in my code that:
-* allows anonymous posting of Q&A and then login
-* per-question email notifications via 'send_email_alerts' command
-* allows space character in username
-* improves openid login
-* makes notification messages sticky - have to click "x" to dismiss
-* unanswered questions are now those with no accepted answer
-* added following setting parameters:
-
-settings.MIN_USERNAME_LENGTH = 1
-settings.EMAIL_UNIQUE = True|False
-settings.EMAIL_VALIDATION = 'on'|'off' #thought of maybe adding other options so type is string
-settings.GOOGLE_SITEMAP_CODE = <string>
-settings.GOOGLE_ANALYTICS_KEY = <string>
-
-===Fixes===
-* fixed incorrect answer count issue in question.html
-* translated Twittwer stuff in user_preferences.html
-* translated question_retag.html, except one phrase
-* added Adolfo's python2.4 fix
-* fixed template debugging comments so that they don't break page layout
-* reorganized header template so that it takes less vertical space
-
-===Code changes===
-* wrapped template context settings into a single settings dictionary
-* on login anonymous session is recorded so that anonymously posted questions (if any) could be found later
-* added models: AnonymousQuestion, AnonymousAnswer, EmailFeed (for notifications)
-* User model has two new fields email_key - 32 byte hex hash and email_isvalid - boolean
- file sql_scripts/update_2009_07_05_EF.sql will make upgrade to the live database
-* added auth_processor to context.py which loads notification messages without deleting them
- NOTE: default django auth processor must be removed!
-* added ajax actions questionSubscribeUpdates/questionUnsubscribeUpdates
-
-==Aug 4 2009, Evgeny==
-===Changes===
-* commented out LocaleMiddleware - language can be now switched with settings.LANGUAGE_CODE
-* added DATABASE_ENGINE line to settings_local
-===Merges===
-* Adolfo's slugification of urls
-* Added Chaitanyas changes for traditional login/signup and INSTALL file
-
-==July 26 2009, Evgeny==
-
-django_authopenid:
-considerably changed user interface
-[comment] - sorry - this is not done yet
-
-log/forum/forms.py:
-* added tag input validation using regex
-* fixed bug with date type mismatch near self.fields['birthday'] =
- in EditUserForm.__init__()
-
-/forum/templatetags/extra_tags.py:
-* fixed date type mismatch in get_age()
-
-/templates/content/js/com.cnprog.post.js:
-* fixed bug with post deletion/recovery
-
-javascript:
-* changed to use of non-minified code - better for editing
-and debugging
-
-/templates/question.html:
-* fixed display of delete/undelete links
-
-templates:
-added comments in the beginning/end of each template
-for the debugging purposes - so that you know which template outputs what html
-<!-- user_favorites.html -->
-<!-- end user_favorites.html -->
diff --git a/django_authopenid/forms.py b/django_authopenid/forms.py
index 5ec21c1c..2f34986c 100644
--- a/django_authopenid/forms.py
+++ b/django_authopenid/forms.py
@@ -39,7 +39,7 @@ import types
import re
from django.utils.safestring import mark_safe
from recaptcha_django import ReCaptchaField
-from utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm
+from forum.utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm
EXTERNAL_LOGIN_APP = settings.LOAD_EXTERNAL_LOGIN_APP()
# needed for some linux distributions like debian
@@ -48,7 +48,7 @@ try:
except ImportError:
from yadis import xri
-from utils.forms import clean_next
+from forum.utils.forms import clean_next
from django_authopenid.models import ExternalLoginData
__all__ = ['OpenidSigninForm', 'ClassicLoginForm', 'OpenidVerifyForm',
diff --git a/django_authopenid/util.py b/django_authopenid/util.py
index c7c9d8f3..1d60d50a 100644
--- a/django_authopenid/util.py
+++ b/django_authopenid/util.py
@@ -15,7 +15,7 @@ except:
from yadis import xri
import time, base64, hashlib, operator, logging
-from utils.forms import clean_next, get_next_url
+from forum.utils.forms import clean_next, get_next_url
from models import Association, Nonce
diff --git a/django_authopenid/views.py b/django_authopenid/views.py
index 16a78864..7c7d9e07 100755
--- a/django_authopenid/views.py
+++ b/django_authopenid/views.py
@@ -67,7 +67,7 @@ from django_authopenid.forms import OpenidSigninForm, ClassicLoginForm, OpenidRe
OpenidVerifyForm, ClassicRegisterForm, ChangePasswordForm, ChangeEmailForm, \
ChangeopenidForm, DeleteForm, EmailPasswordForm
import logging
-from utils.forms import get_next_url
+from forum.utils.forms import get_next_url
EXTERNAL_LOGIN_APP = settings.LOAD_EXTERNAL_LOGIN_APP()
diff --git a/forum/__init__.py b/forum/__init__.py
index a221a3ad..85cd5d26 100644
--- a/forum/__init__.py
+++ b/forum/__init__.py
@@ -1 +1 @@
-__all__ = ['admin','auth','const','diff','feed','forms','managers','models','sitemap','urls','views']
+__all__ = ['admin','auth','const','feed','forms','managers','models','sitemap','urls','views']
diff --git a/forum/admin.py b/forum/admin.py
index 810ae3d0..9d81450a 100644
--- a/forum/admin.py
+++ b/forum/admin.py
@@ -46,6 +46,14 @@ class ReputeAdmin(admin.ModelAdmin):
class ActivityAdmin(admin.ModelAdmin):
""" admin class"""
+#class BookAdmin(admin.ModelAdmin):
+# """ admin class"""
+
+#class BookAuthorInfoAdmin(admin.ModelAdmin):
+# """ admin class"""
+
+#class BookAuthorRssAdmin(admin.ModelAdmin):
+# """ admin class"""
admin.site.register(Question, QuestionAdmin)
admin.site.register(Tag, TagAdmin)
@@ -60,3 +68,6 @@ admin.site.register(Badge, BadgeAdmin)
admin.site.register(Award, AwardAdmin)
admin.site.register(Repute, ReputeAdmin)
admin.site.register(Activity, ActivityAdmin)
+#admin.site.register(Book, BookAdmin)
+#admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin)
+#admin.site.register(BookAuthorRss, BookAuthorRssAdmin)
diff --git a/forum/auth.py b/forum/auth.py
index eb81f853..3533b9ce 100644
--- a/forum/auth.py
+++ b/forum/auth.py
@@ -1,4 +1,4 @@
- """
+"""
Authorisation related functions.
The actions a User is authorised to perform are dependent on their reputation
@@ -19,7 +19,7 @@ answer_type = ContentType.objects.get_for_model(Answer)
VOTE_UP = 15
FLAG_OFFENSIVE = 15
POST_IMAGES = 15
-LEAVE_COMMENTS = 50
+LEAVE_COMMENTS = 50
UPLOAD_FILES = 60
VOTE_DOWN = 100
CLOSE_OWN_QUESTIONS = 250
diff --git a/forum/forms.py b/forum/forms.py
index 22799622..5d70fcff 100644
--- a/forum/forms.py
+++ b/forum/forms.py
@@ -4,7 +4,8 @@ from django import forms
from models import *
from const import *
from django.utils.translation import ugettext as _
-from utils.forms import NextUrlField, UserNameField
+from django.contrib.auth.models import User
+from forum.utils.forms import NextUrlField, UserNameField
from recaptcha_django import ReCaptchaField
from django.conf import settings
import logging
diff --git a/forum/management/__init__.py b/forum/management/__init__.py
index e69de29b..b654caaa 100644
--- a/forum/management/__init__.py
+++ b/forum/management/__init__.py
@@ -0,0 +1,3 @@
+from forum.modules import get_modules_script
+
+get_modules_script('management') \ No newline at end of file
diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py
index 5e1eb3d0..db6c00ac 100644
--- a/forum/management/commands/send_email_alerts.py
+++ b/forum/management/commands/send_email_alerts.py
@@ -9,7 +9,7 @@ from django.utils.translation import ungettext
import datetime
from django.conf import settings
import logging
-from utils.odict import OrderedDict
+from forum.utils.odict import OrderedDict
class Command(NoArgsCommand):
def handle_noargs(self,**options):
diff --git a/forum/managers.py b/forum/managers.py
deleted file mode 100644
index 1705184a..00000000
--- a/forum/managers.py
+++ /dev/null
@@ -1,247 +0,0 @@
-import datetime
-import time
-import logging
-from django.contrib.auth.models import User, UserManager
-from django.db import connection, models, transaction
-from django.db.models import Q
-from forum.models import *
-from urllib import quote, unquote
-
-class QuestionManager(models.Manager):
-
- def update_tags(self, question, tagnames, user):
- """
- Updates Tag associations for a question to match the given
- tagname string.
-
- Returns ``True`` if tag usage counts were updated as a result,
- ``False`` otherwise.
- """
- from forum.models import Tag
- current_tags = list(question.tags.all())
- current_tagnames = set(t.name for t in current_tags)
- updated_tagnames = set(t for t in tagnames.split(' ') if t)
- modified_tags = []
-
- removed_tags = [t for t in current_tags
- if t.name not in updated_tagnames]
- if removed_tags:
- modified_tags.extend(removed_tags)
- question.tags.remove(*removed_tags)
-
- added_tagnames = updated_tagnames - current_tagnames
- if added_tagnames:
- added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
- user)
- modified_tags.extend(added_tags)
- question.tags.add(*added_tags)
-
- if modified_tags:
- Tag.objects.update_use_counts(modified_tags)
- return True
-
- return False
-
- def update_answer_count(self, question):
- """
- Executes an UPDATE query to update denormalised data with the
- number of answers the given question has.
- """
-
- # for some reasons, this Answer class failed to be imported,
- # although we have imported all classes from models on top.
- from forum.models import Answer
- self.filter(id=question.id).update(
- answer_count=Answer.objects.get_answers_from_question(question).filter(deleted=False).count())
-
- def update_view_count(self, question):
- """
- update counter+1 when user browse question page
- """
- self.filter(id=question.id).update(view_count = question.view_count + 1)
-
- def update_favorite_count(self, question):
- """
- update favourite_count for given question
- """
- from forum.models import FavoriteQuestion
- self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
-
- def get_similar_questions(self, question):
- """
- Get 10 similar questions for given one.
- This will search the same tag list for give question(by exactly same string) first.
- Questions with the individual tags will be added to list if above questions are not full.
- """
- #print datetime.datetime.now()
- from forum.models import Question
- questions = list(self.filter(tagnames = question.tagnames, deleted=False).all())
-
- tags_list = question.tags.all()
- for tag in tags_list:
- extend_questions = self.filter(tags__id = tag.id, deleted=False)[:50]
- for item in extend_questions:
- if item not in questions and len(questions) < 10:
- questions.append(item)
-
- #print datetime.datetime.now()
- return questions
-
-class TagManager(models.Manager):
- UPDATE_USED_COUNTS_QUERY = (
- 'UPDATE tag '
- 'SET used_count = ('
- 'SELECT COUNT(*) FROM question_tags '
- 'INNER JOIN question ON question_id=question.id '
- 'WHERE tag_id = tag.id AND question.deleted=False'
- ') '
- 'WHERE id IN (%s)')
-
- def get_valid_tags(self, page_size):
- from forum.models import Tag
- tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size]
- return tags
-
- def get_or_create_multiple(self, names, user):
- """
- Fetches a list of Tags with the given names, creating any Tags
- which don't exist when necesssary.
- """
- tags = list(self.filter(name__in=names))
- #Set all these tag visible
- for tag in tags:
- if tag.deleted:
- tag.deleted = False
- tag.deleted_by = None
- tag.deleted_at = None
- tag.save()
-
- if len(tags) < len(names):
- existing_names = set(tag.name for tag in tags)
- new_names = [name for name in names if name not in existing_names]
- tags.extend([self.create(name=name, created_by=user)
- for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
-
- return tags
-
- def update_use_counts(self, tags):
- """Updates the given Tags with their current use counts."""
- if not tags:
- return
- cursor = connection.cursor()
- query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
- cursor.execute(query, [tag.id for tag in tags])
- transaction.commit_unless_managed()
-
- def get_tags_by_questions(self, questions):
- question_ids = []
- for question in questions:
- question_ids.append(question.id)
-
- question_ids_str = ','.join([str(id) for id in question_ids])
- related_tags = self.extra(
- tables=['tag', 'question_tags'],
- where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"]
- ).distinct()
-
- return related_tags
-
-class AnswerManager(models.Manager):
- GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
- def get_answers_from_question(self, question, user=None, other_orderby = None):
- """
- Retrieves visibile answers for the given question. Delete answers
- are only visibile to the person who deleted them.
- """
- if user is None or not user.is_authenticated():
- q = self.filter(question=question, deleted=False)
- else:
- q = self.filter(Q(question=question),
- Q(deleted=False) | Q(deleted_by=user))
- if other_orderby is None:
- q = q.order_by("-accepted")
- else:
- q = q.order_by("-accepted", other_orderby)
-
- return q
-
- def get_answers_from_questions(self, user_id):
- """
- Retrieves visibile answers for the given question. Which are not included own answers
- """
- cursor = connection.cursor()
- cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
- return cursor.fetchall()
-
-class VoteManager(models.Manager):
- COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1"
- COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1"
- COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = %s"
- def get_up_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- def get_down_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- def get_votes_count_today_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-
-class FlaggedItemManager(models.Manager):
- COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = %s"
- def get_flagged_items_count_today(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-
-class ReputeManager(models.Manager):
- COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = %s"
- def get_reputation_by_upvoted_today(self, user):
- """
- For one user in one day, he can only earn rep till certain score (ep. +200)
- by upvoted(also substracted from upvoted canceled). This is because we need
- to prohibit gaming system by upvoting/cancel again and again.
- """
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-class AwardManager(models.Manager):
- def get_recent_awards(self):
- awards = super(AwardManager, self).extra(
- select={'badge_id': 'badge.id', 'badge_name':'badge.name',
- 'badge_description': 'badge.description', 'badge_type': 'badge.type',
- 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
- },
- tables=['award', 'badge', 'auth_user'],
- order_by=['-awarded_at'],
- where=['auth_user.id=award.user_id AND badge_id=badge.id'],
- ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
- return awards
diff --git a/middleware/__init__.py b/forum/middleware/__init__.py
index e69de29b..e69de29b 100644
--- a/middleware/__init__.py
+++ b/forum/middleware/__init__.py
diff --git a/middleware/anon_user.py b/forum/middleware/anon_user.py
index fa2686f0..e05e6f33 100644
--- a/middleware/anon_user.py
+++ b/forum/middleware/anon_user.py
@@ -1,7 +1,7 @@
from django.http import HttpResponseRedirect
-from utils.forms import get_next_url
+from forum.utils.forms import get_next_url
from django.utils.translation import ugettext as _
-from user_messages import create_message, get_and_delete_messages
+from forum.user_messages import create_message, get_and_delete_messages
from django.conf import settings
import logging
diff --git a/middleware/cancel.py b/forum/middleware/cancel.py
index 51e1b253..15a4371d 100644
--- a/middleware/cancel.py
+++ b/forum/middleware/cancel.py
@@ -1,5 +1,5 @@
from django.http import HttpResponseRedirect
-from utils.forms import get_next_url
+from forum.utils.forms import get_next_url
import logging
class CancelActionMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
diff --git a/middleware/pagesize.py b/forum/middleware/pagesize.py
index f6e6fcfd..f6e6fcfd 100644
--- a/middleware/pagesize.py
+++ b/forum/middleware/pagesize.py
diff --git a/forum/models.py b/forum/models.py
deleted file mode 100644
index 8a24e631..00000000
--- a/forum/models.py
+++ /dev/null
@@ -1,955 +0,0 @@
-# encoding:utf-8
-import datetime
-import hashlib
-from urllib import quote_plus, urlencode
-from django.db import models, IntegrityError
-from django.utils.http import urlquote as django_urlquote
-from django.utils.html import strip_tags
-from django.core.urlresolvers import reverse
-from django.contrib.auth.models import User
-from django.contrib.contenttypes import generic
-from django.contrib.contenttypes.models import ContentType
-from django.template.defaultfilters import slugify
-from django.db.models.signals import post_delete, post_save, pre_save
-from django.utils.translation import ugettext as _
-from django.utils.safestring import mark_safe
-from django.contrib.sitemaps import ping_google
-import django.dispatch
-from django.conf import settings
-import logging
-
-if settings.USE_SPHINX_SEARCH == True:
- from djangosphinx.models import SphinxSearch
-
-from forum.managers import *
-from forum.const import *
-
-def get_object_comments(self):
- comments = self.comments.all().order_by('id')
- return comments
-
-def post_get_last_update_info(self):
- when = self.added_at
- who = self.author
- if self.last_edited_at and self.last_edited_at > when:
- when = self.last_edited_at
- who = self.last_edited_by
- comments = self.comments.all()
- if len(comments) > 0:
- for c in comments:
- if c.added_at > when:
- when = c.added_at
- who = c.user
- return when, who
-
-class EmailFeedSetting(models.Model):
- DELTA_TABLE = {
- 'w':datetime.timedelta(7),
- 'd':datetime.timedelta(1),
- 'n':datetime.timedelta(-1),
- }
- FEED_TYPES = (
- ('q_all',_('Entire forum')),
- ('q_ask',_('Questions that I asked')),
- ('q_ans',_('Questions that I answered')),
- ('q_sel',_('Individually selected questions')),
- )
- UPDATE_FREQUENCY = (
- ('w',_('Weekly')),
- ('d',_('Daily')),
- ('n',_('No email')),
- )
- subscriber = models.ForeignKey(User)
- feed_type = models.CharField(max_length=16,choices=FEED_TYPES)
- frequency = models.CharField(max_length=8,choices=UPDATE_FREQUENCY,default='n')
- added_at = models.DateTimeField(auto_now_add=True)
- reported_at = models.DateTimeField(null=True)
-
- def save(self,*args,**kwargs):
- type = self.feed_type
- subscriber = self.subscriber
- similar = self.__class__.objects.filter(feed_type=type,subscriber=subscriber).exclude(pk=self.id)
- if len(similar) > 0:
- raise IntegrityError('email feed setting already exists')
- super(EmailFeedSetting,self).save(*args,**kwargs)
-
-class Tag(models.Model):
- name = models.CharField(max_length=255, unique=True)
- created_by = models.ForeignKey(User, related_name='created_tags')
- deleted = models.BooleanField(default=False)
- deleted_at = models.DateTimeField(null=True, blank=True)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags')
- # Denormalised data
- used_count = models.PositiveIntegerField(default=0)
-
- objects = TagManager()
-
- class Meta:
- db_table = u'tag'
- ordering = ('-used_count', 'name')
-
- def __unicode__(self):
- return self.name
-
-class Comment(models.Model):
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='comments')
- comment = models.CharField(max_length=300)
- added_at = models.DateTimeField(default=datetime.datetime.now)
-
- class Meta:
- ordering = ('-added_at',)
- db_table = u'comment'
-
- def save(self,**kwargs):
- super(Comment,self).save(**kwargs)
- try:
- ping_google()
- except Exception:
- logging.debug('problem pinging google did you register you sitemap with google?')
-
- def __unicode__(self):
- return self.comment
-
-class Vote(models.Model):
- VOTE_UP = +1
- VOTE_DOWN = -1
- VOTE_CHOICES = (
- (VOTE_UP, u'Up'),
- (VOTE_DOWN, u'Down'),
- )
-
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='votes')
- vote = models.SmallIntegerField(choices=VOTE_CHOICES)
- voted_at = models.DateTimeField(default=datetime.datetime.now)
-
- objects = VoteManager()
-
- class Meta:
- unique_together = ('content_type', 'object_id', 'user')
- db_table = u'vote'
- def __unicode__(self):
- return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote)
-
- def is_upvote(self):
- return self.vote == self.VOTE_UP
-
- def is_downvote(self):
- return self.vote == self.VOTE_DOWN
-
-class FlaggedItem(models.Model):
- """A flag on a Question or Answer indicating offensive content."""
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='flagged_items')
- flagged_at = models.DateTimeField(default=datetime.datetime.now)
-
- objects = FlaggedItemManager()
-
- class Meta:
- unique_together = ('content_type', 'object_id', 'user')
- db_table = u'flagged_item'
- def __unicode__(self):
- return '[%s] flagged at %s' %(self.user, self.flagged_at)
-
-class Question(models.Model):
- title = models.CharField(max_length=300)
- author = models.ForeignKey(User, related_name='questions')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- tags = models.ManyToManyField(Tag, related_name='questions')
- # Status
- wiki = models.BooleanField(default=False)
- wikified_at = models.DateTimeField(null=True, blank=True)
- answer_accepted = models.BooleanField(default=False)
- closed = models.BooleanField(default=False)
- closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')
- closed_at = models.DateTimeField(null=True, blank=True)
- close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)
- deleted = models.BooleanField(default=False)
- deleted_at = models.DateTimeField(null=True, blank=True)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_questions')
- locked = models.BooleanField(default=False)
- locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_questions')
- locked_at = models.DateTimeField(null=True, blank=True)
- followed_by = models.ManyToManyField(User, related_name='followed_questions')
- # Denormalised data
- score = models.IntegerField(default=0)
- vote_up_count = models.IntegerField(default=0)
- vote_down_count = models.IntegerField(default=0)
- answer_count = models.PositiveIntegerField(default=0)
- comment_count = models.PositiveIntegerField(default=0)
- view_count = models.PositiveIntegerField(default=0)
- offensive_flag_count = models.SmallIntegerField(default=0)
- favourite_count = models.PositiveIntegerField(default=0)
- last_edited_at = models.DateTimeField(null=True, blank=True)
- last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_questions')
- last_activity_at = models.DateTimeField(default=datetime.datetime.now)
- last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions')
- tagnames = models.CharField(max_length=125)
- summary = models.CharField(max_length=180)
- html = models.TextField()
- comments = generic.GenericRelation(Comment)
- votes = generic.GenericRelation(Vote)
- flagged_items = generic.GenericRelation(FlaggedItem)
-
- if settings.USE_SPHINX_SEARCH == True:
- search = SphinxSearch(
- index=' '.join(settings.SPHINX_SEARCH_INDICES),
- mode='SPH_MATCH_ALL',
- )
- logging.debug('have sphinx search')
-
- objects = QuestionManager()
-
- def delete(self):
- super(Question, self).delete()
- try:
- ping_google()
- except Exception:
- logging.debug('problem pinging google did you register you sitemap with google?')
-
- def save(self, **kwargs):
- """
- Overridden to manually manage addition of tags when the object
- is first saved.
-
- This is required as we're using ``tagnames`` as the sole means of
- adding and editing tags.
- """
- initial_addition = (self.id is None)
- super(Question, self).save(**kwargs)
- try:
- ping_google()
- except Exception:
- logging.debug('problem pinging google did you register you sitemap with google?')
- if initial_addition:
- tags = Tag.objects.get_or_create_multiple(self.tagname_list(),
- self.author)
- self.tags.add(*tags)
- Tag.objects.update_use_counts(tags)
-
- def tagname_list(self):
- """Creates a list of Tag names from the ``tagnames`` attribute."""
- return [name for name in self.tagnames.split(u' ')]
-
- def tagname_meta_generator(self):
- return u','.join([unicode(tag) for tag in self.tagname_list()])
-
- def get_absolute_url(self):
- return '%s%s' % (reverse('question', args=[self.id]), django_urlquote(slugify(self.title)))
-
- def has_favorite_by_user(self, user):
- if not user.is_authenticated():
- return False
- return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0
-
- def get_answer_count_by_user(self, user_id):
- query_set = Answer.objects.filter(author__id=user_id)
- return query_set.filter(question=self).count()
-
- def get_question_title(self):
- if self.closed:
- attr = CONST['closed']
- elif self.deleted:
- attr = CONST['deleted']
- else:
- attr = None
- if attr is not None:
- return u'%s %s' % (self.title, attr)
- else:
- return self.title
-
- def get_revision_url(self):
- return reverse('question_revisions', args=[self.id])
-
- def get_latest_revision(self):
- return self.revisions.all()[0]
-
- get_comments = get_object_comments
-
- def get_last_update_info(self):
-
- when, who = post_get_last_update_info(self)
-
- answers = self.answers.all()
- if len(answers) > 0:
- for a in answers:
- a_when, a_who = a.get_last_update_info()
- if a_when > when:
- when = a_when
- who = a_who
-
- return when, who
-
- def get_update_summary(self,last_reported_at=None,recipient_email=''):
- edited = False
- if self.last_edited_at and self.last_edited_at > last_reported_at:
- if self.last_edited_by.email != recipient_email:
- edited = True
- comments = []
- for comment in self.comments.all():
- if comment.added_at > last_reported_at and comment.user.email != recipient_email:
- comments.append(comment)
- new_answers = []
- answer_comments = []
- modified_answers = []
- commented_answers = []
- import sets
- commented_answers = sets.Set([])
- for answer in self.answers.all():
- if (answer.added_at > last_reported_at and answer.author.email != recipient_email):
- new_answers.append(answer)
- if (answer.last_edited_at
- and answer.last_edited_at > last_reported_at
- and answer.last_edited_by.email != recipient_email):
- modified_answers.append(answer)
- for comment in answer.comments.all():
- if comment.added_at > last_reported_at and comment.user.email != recipient_email:
- commented_answers.add(answer)
- answer_comments.append(comment)
-
- #create the report
- if edited or new_answers or modified_answers or answer_comments:
- out = []
- if edited:
- out.append(_('%(author)s modified the question') % {'author':self.last_edited_by.username})
- if new_answers:
- names = sets.Set(map(lambda x: x.author.username,new_answers))
- people = ', '.join(names)
- out.append(_('%(people)s posted %(new_answer_count)s new answers') \
- % {'new_answer_count':len(new_answers),'people':people})
- if comments:
- names = sets.Set(map(lambda x: x.user.username,comments))
- people = ', '.join(names)
- out.append(_('%(people)s commented the question') % {'people':people})
- if answer_comments:
- names = sets.Set(map(lambda x: x.user.username,answer_comments))
- people = ', '.join(names)
- if len(commented_answers) > 1:
- out.append(_('%(people)s commented answers') % {'people':people})
- else:
- out.append(_('%(people)s commented an answer') % {'people':people})
- url = settings.APP_URL + self.get_absolute_url()
- retval = '<a href="%s">%s</a>:<br>\n' % (url,self.title)
- out = map(lambda x: '<li>' + x + '</li>',out)
- retval += '<ul>' + '\n'.join(out) + '</ul><br>\n'
- return retval
- else:
- return None
-
- def __unicode__(self):
- return self.title
-
- class Meta:
- db_table = u'question'
-
-class QuestionView(models.Model):
- question = models.ForeignKey(Question, related_name='viewed')
- who = models.ForeignKey(User, related_name='question_views')
- when = models.DateTimeField()
-
-class FavoriteQuestion(models.Model):
- """A favorite Question of a User."""
- question = models.ForeignKey(Question)
- user = models.ForeignKey(User, related_name='user_favorite_questions')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- class Meta:
- db_table = u'favorite_question'
- def __unicode__(self):
- return '[%s] favorited at %s' %(self.user, self.added_at)
-
-class MarkedTag(models.Model):
- TAG_MARK_REASONS = (('good',_('interesting')),('bad',_('ignored')))
- tag = models.ForeignKey(Tag, related_name='user_selections')
- user = models.ForeignKey(User, related_name='tag_selections')
- reason = models.CharField(max_length=16, choices=TAG_MARK_REASONS)
-
-class QuestionRevision(models.Model):
- """A revision of a Question."""
- question = models.ForeignKey(Question, related_name='revisions')
- revision = models.PositiveIntegerField(blank=True)
- title = models.CharField(max_length=300)
- author = models.ForeignKey(User, related_name='question_revisions')
- revised_at = models.DateTimeField()
- tagnames = models.CharField(max_length=125)
- summary = models.CharField(max_length=300, blank=True)
- text = models.TextField()
-
- class Meta:
- db_table = u'question_revision'
- ordering = ('-revision',)
-
- def get_question_title(self):
- return self.question.title
-
- def get_absolute_url(self):
- #print 'in QuestionRevision.get_absolute_url()'
- return reverse('question_revisions', args=[self.question.id])
-
- def save(self, **kwargs):
- """Looks up the next available revision number."""
- if not self.revision:
- self.revision = QuestionRevision.objects.filter(
- question=self.question).values_list('revision',
- flat=True)[0] + 1
- super(QuestionRevision, self).save(**kwargs)
-
- def __unicode__(self):
- return u'revision %s of %s' % (self.revision, self.title)
-
-class AnonymousAnswer(models.Model):
- question = models.ForeignKey(Question, related_name='anonymous_answers')
- session_key = models.CharField(max_length=40) #session id for anonymous questions
- wiki = models.BooleanField(default=False)
- added_at = models.DateTimeField(default=datetime.datetime.now)
- ip_addr = models.IPAddressField(max_length=21) #allow high port numbers
- author = models.ForeignKey(User,null=True)
- text = models.TextField()
- summary = models.CharField(max_length=180)
-
- def publish(self,user):
- from forum.views import create_new_answer
- added_at = datetime.datetime.now()
- #print user.id
- create_new_answer(question=self.question,wiki=self.wiki,
- added_at=added_at,text=self.text,
- author=user)
- self.delete()
-
-class AnonymousQuestion(models.Model):
- title = models.CharField(max_length=300)
- session_key = models.CharField(max_length=40) #session id for anonymous questions
- text = models.TextField()
- summary = models.CharField(max_length=180)
- tagnames = models.CharField(max_length=125)
- wiki = models.BooleanField(default=False)
- added_at = models.DateTimeField(default=datetime.datetime.now)
- ip_addr = models.IPAddressField(max_length=21) #allow high port numbers
- author = models.ForeignKey(User,null=True)
-
- def publish(self,user):
- from forum.views import create_new_question
- added_at = datetime.datetime.now()
- create_new_question(title=self.title, author=user, added_at=added_at,
- wiki=self.wiki, tagnames=self.tagnames,
- summary=self.summary, text=self.text)
- self.delete()
-
-class Answer(models.Model):
- question = models.ForeignKey(Question, related_name='answers')
- author = models.ForeignKey(User, related_name='answers')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- # Status
- wiki = models.BooleanField(default=False)
- wikified_at = models.DateTimeField(null=True, blank=True)
- accepted = models.BooleanField(default=False)
- accepted_at = models.DateTimeField(null=True, blank=True)
- deleted = models.BooleanField(default=False)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_answers')
- locked = models.BooleanField(default=False)
- locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_answers')
- locked_at = models.DateTimeField(null=True, blank=True)
- # Denormalised data
- score = models.IntegerField(default=0)
- vote_up_count = models.IntegerField(default=0)
- vote_down_count = models.IntegerField(default=0)
- comment_count = models.PositiveIntegerField(default=0)
- offensive_flag_count = models.SmallIntegerField(default=0)
- last_edited_at = models.DateTimeField(null=True, blank=True)
- last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_answers')
- html = models.TextField()
- comments = generic.GenericRelation(Comment)
- votes = generic.GenericRelation(Vote)
- flagged_items = generic.GenericRelation(FlaggedItem)
-
- objects = AnswerManager()
-
- get_comments = get_object_comments
- get_last_update_info = post_get_last_update_info
-
- def save(self,**kwargs):
- super(Answer,self).save(**kwargs)
- try:
- ping_google()
- except Exception:
- logging.debug('problem pinging google did you register you sitemap with google?')
-
- def get_user_vote(self, user):
- if user.__class__.__name__ == "AnonymousUser":
- return None
-
- votes = self.votes.filter(user=user)
- if votes and votes.count() > 0:
- return votes[0]
- else:
- return None
-
- def get_latest_revision(self):
- return self.revisions.all()[0]
-
- def get_question_title(self):
- return self.question.title
-
- def get_absolute_url(self):
- return '%s%s#%s' % (reverse('question', args=[self.question.id]), django_urlquote(slugify(self.question.title)), self.id)
-
- class Meta:
- db_table = u'answer'
-
- def __unicode__(self):
- return self.html
-
-class AnswerRevision(models.Model):
- """A revision of an Answer."""
- answer = models.ForeignKey(Answer, related_name='revisions')
- revision = models.PositiveIntegerField()
- author = models.ForeignKey(User, related_name='answer_revisions')
- revised_at = models.DateTimeField()
- summary = models.CharField(max_length=300, blank=True)
- text = models.TextField()
-
- def get_absolute_url(self):
- return reverse('answer_revisions', kwargs={'id':self.answer.id})
-
- def get_question_title(self):
- return self.answer.question.title
-
- class Meta:
- db_table = u'answer_revision'
- ordering = ('-revision',)
-
- def save(self, **kwargs):
- """Looks up the next available revision number if not set."""
- if not self.revision:
- self.revision = AnswerRevision.objects.filter(
- answer=self.answer).values_list('revision',
- flat=True)[0] + 1
- super(AnswerRevision, self).save(**kwargs)
-
-class Badge(models.Model):
- """Awarded for notable actions performed on the site by Users."""
- GOLD = 1
- SILVER = 2
- BRONZE = 3
- TYPE_CHOICES = (
- (GOLD, _('gold')),
- (SILVER, _('silver')),
- (BRONZE, _('bronze')),
- )
-
- name = models.CharField(max_length=50)
- type = models.SmallIntegerField(choices=TYPE_CHOICES)
- slug = models.SlugField(max_length=50, blank=True)
- description = models.CharField(max_length=300)
- multiple = models.BooleanField(default=False)
- # Denormalised data
- awarded_count = models.PositiveIntegerField(default=0)
-
- class Meta:
- db_table = u'badge'
- ordering = ('name',)
- unique_together = ('name', 'type')
-
- def __unicode__(self):
- return u'%s: %s' % (self.get_type_display(), self.name)
-
- def save(self, **kwargs):
- if not self.slug:
- self.slug = self.name#slugify(self.name)
- super(Badge, self).save(**kwargs)
-
- def get_absolute_url(self):
- return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
-
-class Award(models.Model):
- """The awarding of a Badge to a User."""
- user = models.ForeignKey(User, related_name='award_user')
- badge = models.ForeignKey(Badge, related_name='award_badge')
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- awarded_at = models.DateTimeField(default=datetime.datetime.now)
- notified = models.BooleanField(default=False)
- objects = AwardManager()
-
- def __unicode__(self):
- return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
-
- class Meta:
- db_table = u'award'
-
-class Repute(models.Model):
- """The reputation histories for user"""
- user = models.ForeignKey(User)
- positive = models.SmallIntegerField(default=0)
- negative = models.SmallIntegerField(default=0)
- question = models.ForeignKey(Question)
- reputed_at = models.DateTimeField(default=datetime.datetime.now)
- reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
- reputation = models.IntegerField(default=1)
- objects = ReputeManager()
-
- def __unicode__(self):
- return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at)
-
- class Meta:
- db_table = u'repute'
-
-class Activity(models.Model):
- """
- We keep some history data for user activities
- """
- user = models.ForeignKey(User)
- activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY)
- active_at = models.DateTimeField(default=datetime.datetime.now)
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- is_auditted = models.BooleanField(default=False)
-
- def __unicode__(self):
- return u'[%s] was active at %s' % (self.user.username, self.active_at)
-
- class Meta:
- db_table = u'activity'
-
-
-class AnonymousEmail(models.Model):
- #validation key, if used
- key = models.CharField(max_length=32)
- email = models.EmailField(null=False,unique=True)
- isvalid = models.BooleanField(default=False)
-
-# User extend properties
-QUESTIONS_PER_PAGE_CHOICES = (
- (10, u'10'),
- (30, u'30'),
- (50, u'50'),
-)
-
-def user_is_username_taken(cls,username):
- try:
- cls.objects.get(username=username)
- return True
- except cls.MultipleObjectsReturned:
- return True
- except cls.DoesNotExist:
- return False
-
-def user_get_q_sel_email_feed_frequency(self):
- #print 'looking for frequency for user %s' % self
- try:
- feed_setting = EmailFeedSetting.objects.get(subscriber=self,feed_type='q_sel')
- except Exception, e:
- #print 'have error %s' % e.message
- raise e
- #print 'have freq=%s' % feed_setting.frequency
- return feed_setting.frequency
-
-User.add_to_class('is_approved', models.BooleanField(default=False))
-User.add_to_class('email_isvalid', models.BooleanField(default=False))
-User.add_to_class('email_key', models.CharField(max_length=32, null=True))
-User.add_to_class('reputation', models.PositiveIntegerField(default=1))
-User.add_to_class('gravatar', models.CharField(max_length=32))
-User.add_to_class('favorite_questions',
- models.ManyToManyField(Question, through=FavoriteQuestion,
- related_name='favorited_by'))
-User.add_to_class('badges', models.ManyToManyField(Badge, through=Award,
- related_name='awarded_to'))
-User.add_to_class('gold', models.SmallIntegerField(default=0))
-User.add_to_class('silver', models.SmallIntegerField(default=0))
-User.add_to_class('bronze', models.SmallIntegerField(default=0))
-User.add_to_class('questions_per_page',
- models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10))
-User.add_to_class('last_seen',
- models.DateTimeField(default=datetime.datetime.now))
-User.add_to_class('real_name', models.CharField(max_length=100, blank=True))
-User.add_to_class('website', models.URLField(max_length=200, blank=True))
-User.add_to_class('location', models.CharField(max_length=100, blank=True))
-User.add_to_class('date_of_birth', models.DateField(null=True, blank=True))
-User.add_to_class('about', models.TextField(blank=True))
-User.add_to_class('is_username_taken',classmethod(user_is_username_taken))
-User.add_to_class('get_q_sel_email_feed_frequency',user_get_q_sel_email_feed_frequency)
-User.add_to_class('hide_ignored_questions', models.BooleanField(default=False))
-User.add_to_class('tag_filter_setting',
- models.CharField(
- max_length=16,
- choices=TAG_EMAIL_FILTER_CHOICES,
- default='ignored'
- )
- )
-
-# custom signal
-tags_updated = django.dispatch.Signal(providing_args=["question"])
-edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"])
-delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"])
-mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"])
-user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"])
-user_logged_in = django.dispatch.Signal(providing_args=["session"])
-
-
-def get_messages(self):
- messages = []
- for m in self.message_set.all():
- messages.append(m.message)
- return messages
-
-def delete_messages(self):
- self.message_set.all().delete()
-
-def get_profile_url(self):
- """Returns the URL for this User's profile."""
- return '%s%s/' % (reverse('user', args=[self.id]), slugify(self.username))
-
-def get_profile_link(self):
- profile_link = u'<a href="%s">%s</a>' % (self.get_profile_url(),self.username)
- logging.debug('in get profile link %s' % profile_link)
- return mark_safe(profile_link)
-
-User.add_to_class('get_profile_url', get_profile_url)
-User.add_to_class('get_profile_link', get_profile_link)
-User.add_to_class('get_messages', get_messages)
-User.add_to_class('delete_messages', delete_messages)
-
-def calculate_gravatar_hash(instance, **kwargs):
- """Calculates a User's gravatar hash from their email address."""
- if kwargs.get('raw', False):
- return
- instance.gravatar = hashlib.md5(instance.email).hexdigest()
-
-def record_ask_event(instance, created, **kwargs):
- if created:
- activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION)
- activity.save()
-
-def record_answer_event(instance, created, **kwargs):
- if created:
- activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER)
- activity.save()
-
-def record_comment_event(instance, created, **kwargs):
- if created:
- from django.contrib.contenttypes.models import ContentType
- question_type = ContentType.objects.get_for_model(Question)
- question_type_id = question_type.id
- if (instance.content_type_id == question_type_id):
- type = TYPE_ACTIVITY_COMMENT_QUESTION
- else:
- type=TYPE_ACTIVITY_COMMENT_ANSWER
- activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type)
- activity.save()
-
-def record_revision_question_event(instance, created, **kwargs):
- if created and instance.revision <> 1:
- activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION)
- activity.save()
-
-def record_revision_answer_event(instance, created, **kwargs):
- if created and instance.revision <> 1:
- activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER)
- activity.save()
-
-def record_award_event(instance, created, **kwargs):
- """
- After we awarded a badge to user, we need to record this activity and notify user.
- We also recaculate awarded_count of this badge and user information.
- """
- if created:
- activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance,
- activity_type=TYPE_ACTIVITY_PRIZE)
- activity.save()
-
- instance.badge.awarded_count += 1
- instance.badge.save()
-
- if instance.badge.type == Badge.GOLD:
- instance.user.gold += 1
- if instance.badge.type == Badge.SILVER:
- instance.user.silver += 1
- if instance.badge.type == Badge.BRONZE:
- instance.user.bronze += 1
- instance.user.save()
-
-def notify_award_message(instance, created, **kwargs):
- """
- Notify users when they have been awarded badges by using Django message.
- """
- if created:
- user = instance.user
- user.message_set.create(message=u"Congratulations, you have received a badge '%s'" % instance.badge.name)
-
-def record_answer_accepted(instance, created, **kwargs):
- """
- when answer is accepted, we record this for question author - who accepted it.
- """
- if not created and instance.accepted:
- activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \
- content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER)
- activity.save()
-
-def update_last_seen(instance, created, **kwargs):
- """
- when user has activities, we update 'last_seen' time stamp for him
- """
- user = instance.user
- user.last_seen = datetime.datetime.now()
- user.save()
-
-def record_vote(instance, created, **kwargs):
- """
- when user have voted
- """
- if created:
- if instance.vote == 1:
- vote_type = TYPE_ACTIVITY_VOTE_UP
- else:
- vote_type = TYPE_ACTIVITY_VOTE_DOWN
-
- activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type)
- activity.save()
-
-def record_cancel_vote(instance, **kwargs):
- """
- when user canceled vote, the vote will be deleted.
- """
- activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE)
- activity.save()
-
-def record_delete_question(instance, delete_by, **kwargs):
- """
- when user deleted the question
- """
- if instance.__class__ == "Question":
- activity_type = TYPE_ACTIVITY_DELETE_QUESTION
- else:
- activity_type = TYPE_ACTIVITY_DELETE_ANSWER
-
- activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type)
- activity.save()
-
-def record_mark_offensive(instance, mark_by, **kwargs):
- activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE)
- activity.save()
-
-def record_update_tags(question, **kwargs):
- """
- when user updated tags of the question
- """
- activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS)
- activity.save()
-
-def record_favorite_question(instance, created, **kwargs):
- """
- when user add the question in him favorite questions list.
- """
- if created:
- activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE)
- activity.save()
-
-def record_user_full_updated(instance, **kwargs):
- activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED)
- activity.save()
-
-def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs):
- aq_list = AnonymousQuestion.objects.filter(session_key = session_key)
- aa_list = AnonymousAnswer.objects.filter(session_key = session_key)
- import settings
- if settings.EMAIL_VALIDATION == 'on':#add user to the record
- for aq in aq_list:
- aq.author = user
- aq.save()
- for aa in aa_list:
- aa.author = user
- aa.save()
- #maybe add pending posts message?
- else: #just publish the questions
- for aq in aq_list:
- aq.publish(user)
- for aa in aa_list:
- aa.publish(user)
-
-#signal for User modle save changes
-pre_save.connect(calculate_gravatar_hash, sender=User)
-post_save.connect(record_ask_event, sender=Question)
-post_save.connect(record_answer_event, sender=Answer)
-post_save.connect(record_comment_event, sender=Comment)
-post_save.connect(record_revision_question_event, sender=QuestionRevision)
-post_save.connect(record_revision_answer_event, sender=AnswerRevision)
-post_save.connect(record_award_event, sender=Award)
-post_save.connect(notify_award_message, sender=Award)
-post_save.connect(record_answer_accepted, sender=Answer)
-post_save.connect(update_last_seen, sender=Activity)
-post_save.connect(record_vote, sender=Vote)
-post_delete.connect(record_cancel_vote, sender=Vote)
-delete_post_or_answer.connect(record_delete_question, sender=Question)
-delete_post_or_answer.connect(record_delete_question, sender=Answer)
-mark_offensive.connect(record_mark_offensive, sender=Question)
-mark_offensive.connect(record_mark_offensive, sender=Answer)
-tags_updated.connect(record_update_tags, sender=Question)
-post_save.connect(record_favorite_question, sender=FavoriteQuestion)
-user_updated.connect(record_user_full_updated, sender=User)
-user_logged_in.connect(post_stored_anonymous_content)
-
-#todo later split this out to the books extension models
-#from django.db import models
-#from django.contrib.auth.models import User
-#from forum.models import Question
-
-class Book(models.Model):
- """
- Model for book info
- """
- user = models.ForeignKey(User)
- title = models.CharField(max_length=255)
- short_name = models.CharField(max_length=255)
- author = models.CharField(max_length=255)
- price = models.DecimalField(max_digits=6, decimal_places=2)
- pages = models.SmallIntegerField()
- published_at = models.DateTimeField()
- publication = models.CharField(max_length=255)
- cover_img = models.CharField(max_length=255)
- tagnames = models.CharField(max_length=125)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
- questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
-
- def get_absolute_url(self):
- return reverse('book', args=[django_urlquote(slugify(self.short_name))])
-
- def __unicode__(self):
- return self.title
- class Meta:
- db_table = u'book'
-
-class BookAuthorInfo(models.Model):
- """
- Model for book author info
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- blog_url = models.CharField(max_length=255)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
-
- class Meta:
- db_table = u'book_author_info'
-
-class BookAuthorRss(models.Model):
- """
- Model for book author blog rss
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- title = models.CharField(max_length=255)
- url = models.CharField(max_length=255)
- rss_created_at = models.DateTimeField()
- added_at = models.DateTimeField()
-
- class Meta:
- db_table = u'book_author_rss'
diff --git a/forum/models/__init__.py b/forum/models/__init__.py
new file mode 100755
index 00000000..9b504103
--- /dev/null
+++ b/forum/models/__init__.py
@@ -0,0 +1,341 @@
+from question import Question ,QuestionRevision, QuestionView, AnonymousQuestion, FavoriteQuestion
+from answer import Answer, AnonymousAnswer, AnswerRevision
+from tag import Tag, MarkedTag
+from meta import Vote, Comment, FlaggedItem
+from user import Activity, AnonymousEmail, EmailFeedSetting
+from repute import Badge, Award, Repute
+
+from base import *
+
+# User extend properties
+QUESTIONS_PER_PAGE_CHOICES = (
+ (10, u'10'),
+ (30, u'30'),
+ (50, u'50'),
+)
+
+def user_is_username_taken(cls,username):
+ try:
+ cls.objects.get(username=username)
+ return True
+ except cls.MultipleObjectsReturned:
+ return True
+ except cls.DoesNotExist:
+ return False
+
+def user_get_q_sel_email_feed_frequency(self):
+ #print 'looking for frequency for user %s' % self
+ try:
+ feed_setting = EmailFeedSetting.objects.get(subscriber=self,feed_type='q_sel')
+ except Exception, e:
+ #print 'have error %s' % e.message
+ raise e
+ #print 'have freq=%s' % feed_setting.frequency
+ return feed_setting.frequency
+
+User.add_to_class('is_approved', models.BooleanField(default=False))
+User.add_to_class('email_isvalid', models.BooleanField(default=False))
+User.add_to_class('email_key', models.CharField(max_length=32, null=True))
+User.add_to_class('reputation', models.PositiveIntegerField(default=1))
+User.add_to_class('gravatar', models.CharField(max_length=32))
+
+#User.add_to_class('favorite_questions',
+# models.ManyToManyField(Question, through=FavoriteQuestion,
+# related_name='favorited_by'))
+
+#User.add_to_class('badges', models.ManyToManyField(Badge, through=Award,
+# related_name='awarded_to'))
+User.add_to_class('gold', models.SmallIntegerField(default=0))
+User.add_to_class('silver', models.SmallIntegerField(default=0))
+User.add_to_class('bronze', models.SmallIntegerField(default=0))
+User.add_to_class('questions_per_page',
+ models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10))
+User.add_to_class('last_seen',
+ models.DateTimeField(default=datetime.datetime.now))
+User.add_to_class('real_name', models.CharField(max_length=100, blank=True))
+User.add_to_class('website', models.URLField(max_length=200, blank=True))
+User.add_to_class('location', models.CharField(max_length=100, blank=True))
+User.add_to_class('date_of_birth', models.DateField(null=True, blank=True))
+User.add_to_class('about', models.TextField(blank=True))
+User.add_to_class('is_username_taken',classmethod(user_is_username_taken))
+User.add_to_class('get_q_sel_email_feed_frequency',user_get_q_sel_email_feed_frequency)
+User.add_to_class('hide_ignored_questions', models.BooleanField(default=False))
+User.add_to_class('tag_filter_setting',
+ models.CharField(
+ max_length=16,
+ choices=TAG_EMAIL_FILTER_CHOICES,
+ default='ignored'
+ )
+ )
+
+# custom signal
+tags_updated = django.dispatch.Signal(providing_args=["question"])
+edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"])
+delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"])
+mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"])
+user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"])
+user_logged_in = django.dispatch.Signal(providing_args=["session"])
+
+
+def get_messages(self):
+ messages = []
+ for m in self.message_set.all():
+ messages.append(m.message)
+ return messages
+
+def delete_messages(self):
+ self.message_set.all().delete()
+
+def get_profile_url(self):
+ """Returns the URL for this User's profile."""
+ return '%s%s/' % (reverse('user', args=[self.id]), slugify(self.username))
+
+def get_profile_link(self):
+ profile_link = u'<a href="%s">%s</a>' % (self.get_profile_url(),self.username)
+ logging.debug('in get profile link %s' % profile_link)
+ return mark_safe(profile_link)
+
+User.add_to_class('get_profile_url', get_profile_url)
+User.add_to_class('get_profile_link', get_profile_link)
+User.add_to_class('get_messages', get_messages)
+User.add_to_class('delete_messages', delete_messages)
+
+def calculate_gravatar_hash(instance, **kwargs):
+ """Calculates a User's gravatar hash from their email address."""
+ if kwargs.get('raw', False):
+ return
+ instance.gravatar = hashlib.md5(instance.email).hexdigest()
+
+def record_ask_event(instance, created, **kwargs):
+ if created:
+ activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION)
+ activity.save()
+
+def record_answer_event(instance, created, **kwargs):
+ if created:
+ activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER)
+ activity.save()
+
+def record_comment_event(instance, created, **kwargs):
+ if created:
+ from django.contrib.contenttypes.models import ContentType
+ question_type = ContentType.objects.get_for_model(Question)
+ question_type_id = question_type.id
+ if (instance.content_type_id == question_type_id):
+ type = TYPE_ACTIVITY_COMMENT_QUESTION
+ else:
+ type = TYPE_ACTIVITY_COMMENT_ANSWER
+ activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type)
+ activity.save()
+
+def record_revision_question_event(instance, created, **kwargs):
+ if created and instance.revision <> 1:
+ activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION)
+ activity.save()
+
+def record_revision_answer_event(instance, created, **kwargs):
+ if created and instance.revision <> 1:
+ activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER)
+ activity.save()
+
+def record_award_event(instance, created, **kwargs):
+ """
+ After we awarded a badge to user, we need to record this activity and notify user.
+ We also recaculate awarded_count of this badge and user information.
+ """
+ if created:
+ activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance,
+ activity_type=TYPE_ACTIVITY_PRIZE)
+ activity.save()
+
+ instance.badge.awarded_count += 1
+ instance.badge.save()
+
+ if instance.badge.type == Badge.GOLD:
+ instance.user.gold += 1
+ if instance.badge.type == Badge.SILVER:
+ instance.user.silver += 1
+ if instance.badge.type == Badge.BRONZE:
+ instance.user.bronze += 1
+ instance.user.save()
+
+def notify_award_message(instance, created, **kwargs):
+ """
+ Notify users when they have been awarded badges by using Django message.
+ """
+ if created:
+ user = instance.user
+ user.message_set.create(message=u"Congratulations, you have received a badge '%s'" % instance.badge.name)
+
+def record_answer_accepted(instance, created, **kwargs):
+ """
+ when answer is accepted, we record this for question author - who accepted it.
+ """
+ if not created and instance.accepted:
+ activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \
+ content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER)
+ activity.save()
+
+def update_last_seen(instance, created, **kwargs):
+ """
+ when user has activities, we update 'last_seen' time stamp for him
+ """
+ user = instance.user
+ user.last_seen = datetime.datetime.now()
+ user.save()
+
+def record_vote(instance, created, **kwargs):
+ """
+ when user have voted
+ """
+ if created:
+ if instance.vote == 1:
+ vote_type = TYPE_ACTIVITY_VOTE_UP
+ else:
+ vote_type = TYPE_ACTIVITY_VOTE_DOWN
+
+ activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type)
+ activity.save()
+
+def record_cancel_vote(instance, **kwargs):
+ """
+ when user canceled vote, the vote will be deleted.
+ """
+ activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE)
+ activity.save()
+
+def record_delete_question(instance, delete_by, **kwargs):
+ """
+ when user deleted the question
+ """
+ if instance.__class__ == "Question":
+ activity_type = TYPE_ACTIVITY_DELETE_QUESTION
+ else:
+ activity_type = TYPE_ACTIVITY_DELETE_ANSWER
+
+ activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type)
+ activity.save()
+
+def record_mark_offensive(instance, mark_by, **kwargs):
+ activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE)
+ activity.save()
+
+def record_update_tags(question, **kwargs):
+ """
+ when user updated tags of the question
+ """
+ activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS)
+ activity.save()
+
+def record_favorite_question(instance, created, **kwargs):
+ """
+ when user add the question in him favorite questions list.
+ """
+ if created:
+ activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE)
+ activity.save()
+
+def record_user_full_updated(instance, **kwargs):
+ activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED)
+ activity.save()
+
+def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs):
+ aq_list = AnonymousQuestion.objects.filter(session_key = session_key)
+ aa_list = AnonymousAnswer.objects.filter(session_key = session_key)
+ import settings
+ if settings.EMAIL_VALIDATION == 'on':#add user to the record
+ for aq in aq_list:
+ aq.author = user
+ aq.save()
+ for aa in aa_list:
+ aa.author = user
+ aa.save()
+ #maybe add pending posts message?
+ else: #just publish the questions
+ for aq in aq_list:
+ aq.publish(user)
+ for aa in aa_list:
+ aa.publish(user)
+
+#signal for User modle save changes
+
+pre_save.connect(calculate_gravatar_hash, sender=User)
+post_save.connect(record_ask_event, sender=Question)
+post_save.connect(record_answer_event, sender=Answer)
+post_save.connect(record_comment_event, sender=Comment)
+post_save.connect(record_revision_question_event, sender=QuestionRevision)
+post_save.connect(record_revision_answer_event, sender=AnswerRevision)
+post_save.connect(record_award_event, sender=Award)
+post_save.connect(notify_award_message, sender=Award)
+post_save.connect(record_answer_accepted, sender=Answer)
+post_save.connect(update_last_seen, sender=Activity)
+post_save.connect(record_vote, sender=Vote)
+post_delete.connect(record_cancel_vote, sender=Vote)
+delete_post_or_answer.connect(record_delete_question, sender=Question)
+delete_post_or_answer.connect(record_delete_question, sender=Answer)
+mark_offensive.connect(record_mark_offensive, sender=Question)
+mark_offensive.connect(record_mark_offensive, sender=Answer)
+tags_updated.connect(record_update_tags, sender=Question)
+post_save.connect(record_favorite_question, sender=FavoriteQuestion)
+user_updated.connect(record_user_full_updated, sender=User)
+user_logged_in.connect(post_stored_anonymous_content)
+
+Question = Question
+QuestionRevision = QuestionRevision
+QuestionView = QuestionView
+FavoriteQuestion = FavoriteQuestion
+AnonymousQuestion = AnonymousQuestion
+
+Answer = Answer
+AnswerRevision = AnswerRevision
+AnonymousAnswer = AnonymousAnswer
+
+Tag = Tag
+Comment = Comment
+Vote = Vote
+FlaggedItem = FlaggedItem
+MarkedTag = MarkedTag
+
+Badge = Badge
+Award = Award
+Repute = Repute
+
+Activity = Activity
+EmailFeedSetting = EmailFeedSetting
+AnonymousEmail = AnonymousEmail
+
+__all__ = [
+ 'Question',
+ 'QuestionRevision',
+ 'QuestionView',
+ 'FavoriteQuestion',
+ 'AnonymousQuestion',
+
+ 'Answer',
+ 'AnswerRevision',
+ 'AnonymousAnswer',
+
+ 'Tag',
+ 'Comment',
+ 'Vote',
+ 'FlaggedItem',
+ 'MarkedTag',
+
+ 'Badge',
+ 'Award',
+ 'Repute',
+
+ 'Activity',
+ 'EmailFeedSetting',
+ 'AnonymousEmail',
+
+ 'User'
+ ]
+
+
+from forum.modules import get_modules_script_classes
+
+for k, v in get_modules_script_classes('models', models.Model).items():
+ if not k in __all__:
+ __all__.append(k)
+ exec "%s = v" % k \ No newline at end of file
diff --git a/forum/models/answer.py b/forum/models/answer.py
new file mode 100755
index 00000000..16e55c69
--- /dev/null
+++ b/forum/models/answer.py
@@ -0,0 +1,133 @@
+from base import *
+
+from question import Question
+
+class AnswerManager(models.Manager):
+ def create_new(self, question=None, author=None, added_at=None, wiki=False, text='', email_notify=False):
+ answer = Answer(
+ question = question,
+ author = author,
+ added_at = added_at,
+ wiki = wiki,
+ html = text
+ )
+ if answer.wiki:
+ answer.last_edited_by = answer.author
+ answer.last_edited_at = added_at
+ answer.wikified_at = added_at
+
+ answer.save()
+
+ #update question data
+ question.last_activity_at = added_at
+ question.last_activity_by = author
+ question.save()
+ Question.objects.update_answer_count(question)
+
+ AnswerRevision.objects.create(
+ answer = answer,
+ revision = 1,
+ author = author,
+ revised_at = added_at,
+ summary = CONST['default_version'],
+ text = text
+ )
+
+ #set notification/delete
+ if email_notify:
+ if author not in question.followed_by.all():
+ question.followed_by.add(author)
+ else:
+ #not sure if this is necessary. ajax should take care of this...
+ try:
+ question.followed_by.remove(author)
+ except:
+ pass
+
+ #GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
+ def get_answers_from_question(self, question, user=None):
+ """
+ Retrieves visibile answers for the given question. Delete answers
+ are only visibile to the person who deleted them.
+ """
+
+ if user is None or not user.is_authenticated():
+ return self.filter(question=question, deleted=False)
+ else:
+ return self.filter(models.Q(question=question),
+ models.Q(deleted=False) | models.Q(deleted_by=user))
+
+ #todo: I think this method is not being used anymore, I'll just comment it for now
+ #def get_answers_from_questions(self, user_id):
+ # """
+ # Retrieves visibile answers for the given question. Which are not included own answers
+ # """
+ # cursor = connection.cursor()
+ # cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
+ # return cursor.fetchall()
+
+class Answer(Content, DeletableContent):
+ question = models.ForeignKey('Question', related_name='answers')
+ accepted = models.BooleanField(default=False)
+ accepted_at = models.DateTimeField(null=True, blank=True)
+
+ objects = AnswerManager()
+
+ class Meta(Content.Meta):
+ db_table = u'answer'
+
+ def get_user_vote(self, user):
+ if user.__class__.__name__ == "AnonymousUser":
+ return None
+
+ votes = self.votes.filter(user=user)
+ if votes and votes.count() > 0:
+ return votes[0]
+ else:
+ return None
+
+ def get_latest_revision(self):
+ return self.revisions.all()[0]
+
+ def get_question_title(self):
+ return self.question.title
+
+ def get_absolute_url(self):
+ return '%s%s#%s' % (reverse('question', args=[self.question.id]), django_urlquote(slugify(self.question.title)), self.id)
+
+ def __unicode__(self):
+ return self.html
+
+
+class AnswerRevision(ContentRevision):
+ """A revision of an Answer."""
+ answer = models.ForeignKey('Answer', related_name='revisions')
+
+ def get_absolute_url(self):
+ return reverse('answer_revisions', kwargs={'id':self.answer.id})
+
+ def get_question_title(self):
+ return self.answer.question.title
+
+ class Meta(ContentRevision.Meta):
+ db_table = u'answer_revision'
+ ordering = ('-revision',)
+
+ def save(self, **kwargs):
+ """Looks up the next available revision number if not set."""
+ if not self.revision:
+ self.revision = AnswerRevision.objects.filter(
+ answer=self.answer).values_list('revision',
+ flat=True)[0] + 1
+ super(AnswerRevision, self).save(**kwargs)
+
+class AnonymousAnswer(AnonymousContent):
+ question = models.ForeignKey('Question', related_name='anonymous_answers')
+
+ def publish(self,user):
+ added_at = datetime.datetime.now()
+ #print user.id
+ AnswerManager.create_new(question=self.question,wiki=self.wiki,
+ added_at=added_at,text=self.text,
+ author=user)
+ self.delete()
diff --git a/forum/models/base.py b/forum/models/base.py
new file mode 100755
index 00000000..44fa6e66
--- /dev/null
+++ b/forum/models/base.py
@@ -0,0 +1,139 @@
+import datetime
+import hashlib
+from urllib import quote_plus, urlencode
+from django.db import models, IntegrityError, connection, transaction
+from django.utils.http import urlquote as django_urlquote
+from django.utils.html import strip_tags
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.template.defaultfilters import slugify
+from django.db.models.signals import post_delete, post_save, pre_save
+from django.utils.translation import ugettext as _
+from django.utils.safestring import mark_safe
+from django.contrib.sitemaps import ping_google
+import django.dispatch
+from django.conf import settings
+import logging
+
+if settings.USE_SPHINX_SEARCH == True:
+ from djangosphinx.models import SphinxSearch
+
+from forum.const import *
+
+class MetaContent(models.Model):
+ """
+ Base class for Vote, Comment and FlaggedItem
+ """
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='%(class)ss')
+
+ class Meta:
+ abstract = True
+ app_label = 'forum'
+
+
+class DeletableContent(models.Model):
+ deleted = models.BooleanField(default=False)
+ deleted_at = models.DateTimeField(null=True, blank=True)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_%(class)ss')
+
+ class Meta:
+ abstract = True
+ app_label = 'forum'
+
+
+class ContentRevision(models.Model):
+ """
+ Base class for QuestionRevision and AnswerRevision
+ """
+ revision = models.PositiveIntegerField()
+ author = models.ForeignKey(User, related_name='%(class)ss')
+ revised_at = models.DateTimeField()
+ summary = models.CharField(max_length=300, blank=True)
+ text = models.TextField()
+
+ class Meta:
+ abstract = True
+ app_label = 'forum'
+
+
+class AnonymousContent(models.Model):
+ """
+ Base class for AnonymousQuestion and AnonymousAnswer
+ """
+ session_key = models.CharField(max_length=40) #session id for anonymous questions
+ wiki = models.BooleanField(default=False)
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ ip_addr = models.IPAddressField(max_length=21) #allow high port numbers
+ author = models.ForeignKey(User,null=True)
+ text = models.TextField()
+ summary = models.CharField(max_length=180)
+
+ class Meta:
+ abstract = True
+ app_label = 'forum'
+
+
+from meta import Comment, Vote, FlaggedItem
+
+class Content(models.Model):
+ """
+ Base class for Question and Answer
+ """
+ author = models.ForeignKey(User, related_name='%(class)ss')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+
+ wiki = models.BooleanField(default=False)
+ wikified_at = models.DateTimeField(null=True, blank=True)
+
+ locked = models.BooleanField(default=False)
+ locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_%(class)ss')
+ locked_at = models.DateTimeField(null=True, blank=True)
+
+ score = models.IntegerField(default=0)
+ vote_up_count = models.IntegerField(default=0)
+ vote_down_count = models.IntegerField(default=0)
+
+ comment_count = models.PositiveIntegerField(default=0)
+ offensive_flag_count = models.SmallIntegerField(default=0)
+
+ last_edited_at = models.DateTimeField(null=True, blank=True)
+ last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_%(class)ss')
+
+ html = models.TextField()
+ comments = generic.GenericRelation(Comment)
+ votes = generic.GenericRelation(Vote)
+ flagged_items = generic.GenericRelation(FlaggedItem)
+
+ class Meta:
+ abstract = True
+ app_label = 'forum'
+
+ def save(self,**kwargs):
+ super(Content,self).save(**kwargs)
+ try:
+ ping_google()
+ except Exception:
+ logging.debug('problem pinging google did you register you sitemap with google?')
+
+ def get_object_comments(self):
+ comments = self.comments.all().order_by('id')
+ return comments
+
+ def post_get_last_update_info(self):
+ when = self.added_at
+ who = self.author
+ if self.last_edited_at and self.last_edited_at > when:
+ when = self.last_edited_at
+ who = self.last_edited_by
+ comments = self.comments.all()
+ if len(comments) > 0:
+ for c in comments:
+ if c.added_at > when:
+ when = c.added_at
+ who = c.user
+ return when, who \ No newline at end of file
diff --git a/forum/models/meta.py b/forum/models/meta.py
new file mode 100755
index 00000000..7c3f5d36
--- /dev/null
+++ b/forum/models/meta.py
@@ -0,0 +1,89 @@
+from base import *
+
+class VoteManager(models.Manager):
+ def get_up_vote_count_from_user(self, user):
+ if user is not None:
+ return self.filter(user=user, vote=1).count()
+ else:
+ return 0
+
+ def get_down_vote_count_from_user(self, user):
+ if user is not None:
+ return self.filter(user=user, vote=-1).count()
+ else:
+ return 0
+
+ def get_votes_count_today_from_user(self, user):
+ if user is not None:
+ today = datetime.date.today()
+ return self.filter(user=user, voted_at__range=(today, today + datetime.timedelta(1))).count()
+
+ else:
+ return 0
+
+
+class Vote(MetaContent):
+ VOTE_UP = +1
+ VOTE_DOWN = -1
+ VOTE_CHOICES = (
+ (VOTE_UP, u'Up'),
+ (VOTE_DOWN, u'Down'),
+ )
+
+ vote = models.SmallIntegerField(choices=VOTE_CHOICES)
+ voted_at = models.DateTimeField(default=datetime.datetime.now)
+
+ objects = VoteManager()
+
+ class Meta(MetaContent.Meta):
+ unique_together = ('content_type', 'object_id', 'user')
+ db_table = u'vote'
+
+ def __unicode__(self):
+ return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote)
+
+ def is_upvote(self):
+ return self.vote == self.VOTE_UP
+
+ def is_downvote(self):
+ return self.vote == self.VOTE_DOWN
+
+
+class FlaggedItemManager(models.Manager):
+ def get_flagged_items_count_today(self, user):
+ if user is not None:
+ today = datetime.date.today()
+ return self.filter(user=user, flagged_at__range=(today, today + datetime.timedelta(1))).count()
+ else:
+ return 0
+
+class FlaggedItem(MetaContent):
+ """A flag on a Question or Answer indicating offensive content."""
+ flagged_at = models.DateTimeField(default=datetime.datetime.now)
+
+ objects = FlaggedItemManager()
+
+ class Meta(MetaContent.Meta):
+ unique_together = ('content_type', 'object_id', 'user')
+ db_table = u'flagged_item'
+
+ def __unicode__(self):
+ return '[%s] flagged at %s' %(self.user, self.flagged_at)
+
+class Comment(MetaContent):
+ comment = models.CharField(max_length=300)
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+
+ class Meta(MetaContent.Meta):
+ ordering = ('-added_at',)
+ db_table = u'comment'
+
+ def save(self,**kwargs):
+ super(Comment,self).save(**kwargs)
+ try:
+ ping_google()
+ except Exception:
+ logging.debug('problem pinging google did you register you sitemap with google?')
+
+ def __unicode__(self):
+ return self.comment \ No newline at end of file
diff --git a/forum/models/question.py b/forum/models/question.py
new file mode 100755
index 00000000..37924a5a
--- /dev/null
+++ b/forum/models/question.py
@@ -0,0 +1,335 @@
+from base import *
+from tag import Tag
+
+class QuestionManager(models.Manager):
+ def create_new(self, title=None,author=None,added_at=None, wiki=False,tagnames=None,summary=None, text=None):
+ question = Question(
+ title = title,
+ author = author,
+ added_at = added_at,
+ last_activity_at = added_at,
+ last_activity_by = author,
+ wiki = wiki,
+ tagnames = tagnames,
+ html = text,
+ summary = summary
+ )
+ if question.wiki:
+ question.last_edited_by = question.author
+ question.last_edited_at = added_at
+ question.wikified_at = added_at
+
+ question.save()
+
+ # create the first revision
+ QuestionRevision.objects.create(
+ question = question,
+ revision = 1,
+ title = question.title,
+ author = author,
+ revised_at = added_at,
+ tagnames = question.tagnames,
+ summary = CONST['default_version'],
+ text = text
+ )
+ return question
+
+ def update_tags(self, question, tagnames, user):
+ """
+ Updates Tag associations for a question to match the given
+ tagname string.
+
+ Returns ``True`` if tag usage counts were updated as a result,
+ ``False`` otherwise.
+ """
+
+ current_tags = list(question.tags.all())
+ current_tagnames = set(t.name for t in current_tags)
+ updated_tagnames = set(t for t in tagnames.split(' ') if t)
+ modified_tags = []
+
+ removed_tags = [t for t in current_tags
+ if t.name not in updated_tagnames]
+ if removed_tags:
+ modified_tags.extend(removed_tags)
+ question.tags.remove(*removed_tags)
+
+ added_tagnames = updated_tagnames - current_tagnames
+ if added_tagnames:
+ added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
+ user)
+ modified_tags.extend(added_tags)
+ question.tags.add(*added_tags)
+
+ if modified_tags:
+ Tag.objects.update_use_counts(modified_tags)
+ return True
+
+ return False
+
+ def update_answer_count(self, question):
+ """
+ Executes an UPDATE query to update denormalised data with the
+ number of answers the given question has.
+ """
+
+ # for some reasons, this Answer class failed to be imported,
+ # although we have imported all classes from models on top.
+ from answer import Answer
+ self.filter(id=question.id).update(
+ answer_count=Answer.objects.get_answers_from_question(question).filter(deleted=False).count())
+
+ def update_view_count(self, question):
+ """
+ update counter+1 when user browse question page
+ """
+ self.filter(id=question.id).update(view_count = question.view_count + 1)
+
+ def update_favorite_count(self, question):
+ """
+ update favourite_count for given question
+ """
+ self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
+
+ def get_similar_questions(self, question):
+ """
+ Get 10 similar questions for given one.
+ This will search the same tag list for give question(by exactly same string) first.
+ Questions with the individual tags will be added to list if above questions are not full.
+ """
+ #print datetime.datetime.now()
+ questions = list(self.filter(tagnames = question.tagnames, deleted=False).all())
+
+ tags_list = question.tags.all()
+ for tag in tags_list:
+ extend_questions = self.filter(tags__id = tag.id, deleted=False)[:50]
+ for item in extend_questions:
+ if item not in questions and len(questions) < 10:
+ questions.append(item)
+
+ #print datetime.datetime.now()
+ return questions
+
+class Question(Content, DeletableContent):
+ title = models.CharField(max_length=300)
+ tags = models.ManyToManyField('Tag', related_name='questions')
+ answer_accepted = models.BooleanField(default=False)
+ closed = models.BooleanField(default=False)
+ closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')
+ closed_at = models.DateTimeField(null=True, blank=True)
+ close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)
+ followed_by = models.ManyToManyField(User, related_name='followed_questions')
+
+ # Denormalised data
+ answer_count = models.PositiveIntegerField(default=0)
+ view_count = models.PositiveIntegerField(default=0)
+ favourite_count = models.PositiveIntegerField(default=0)
+ last_activity_at = models.DateTimeField(default=datetime.datetime.now)
+ last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions')
+ tagnames = models.CharField(max_length=125)
+ summary = models.CharField(max_length=180)
+
+ favorited_by = models.ManyToManyField(User, through='FavoriteQuestion', related_name='favorite_questions')
+
+ objects = QuestionManager()
+
+ class Meta(Content.Meta):
+ db_table = u'question'
+
+ def delete(self):
+ super(Question, self).delete()
+ try:
+ ping_google()
+ except Exception:
+ logging.debug('problem pinging google did you register you sitemap with google?')
+
+ def save(self, **kwargs):
+ """
+ Overridden to manually manage addition of tags when the object
+ is first saved.
+
+ This is required as we're using ``tagnames`` as the sole means of
+ adding and editing tags.
+ """
+ initial_addition = (self.id is None)
+
+ super(Question, self).save(**kwargs)
+
+ if initial_addition:
+ tags = Tag.objects.get_or_create_multiple(self.tagname_list(),
+ self.author)
+ self.tags.add(*tags)
+ Tag.objects.update_use_counts(tags)
+
+ def tagname_list(self):
+ """Creates a list of Tag names from the ``tagnames`` attribute."""
+ return [name for name in self.tagnames.split(u' ')]
+
+ def tagname_meta_generator(self):
+ return u','.join([unicode(tag) for tag in self.tagname_list()])
+
+ def get_absolute_url(self):
+ return '%s%s' % (reverse('question', args=[self.id]), django_urlquote(slugify(self.title)))
+
+ def has_favorite_by_user(self, user):
+ if not user.is_authenticated():
+ return False
+
+ return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0
+
+ def get_answer_count_by_user(self, user_id):
+ from answer import Answer
+ query_set = Answer.objects.filter(author__id=user_id)
+ return query_set.filter(question=self).count()
+
+ def get_question_title(self):
+ if self.closed:
+ attr = CONST['closed']
+ elif self.deleted:
+ attr = CONST['deleted']
+ else:
+ attr = None
+ if attr is not None:
+ return u'%s %s' % (self.title, attr)
+ else:
+ return self.title
+
+ def get_revision_url(self):
+ return reverse('question_revisions', args=[self.id])
+
+ def get_latest_revision(self):
+ return self.revisions.all()[0]
+
+ def get_last_update_info(self):
+ when, who = self.post_get_last_update_info()
+
+ answers = self.answers.all()
+ if len(answers) > 0:
+ for a in answers:
+ a_when, a_who = a.post_get_last_update_info()
+ if a_when > when:
+ when = a_when
+ who = a_who
+
+ return when, who
+
+ def get_update_summary(self,last_reported_at=None,recipient_email=''):
+ edited = False
+ if self.last_edited_at and self.last_edited_at > last_reported_at:
+ if self.last_edited_by.email != recipient_email:
+ edited = True
+ comments = []
+ for comment in self.comments.all():
+ if comment.added_at > last_reported_at and comment.user.email != recipient_email:
+ comments.append(comment)
+ new_answers = []
+ answer_comments = []
+ modified_answers = []
+ commented_answers = []
+ import sets
+ commented_answers = sets.Set([])
+ for answer in self.answers.all():
+ if (answer.added_at > last_reported_at and answer.author.email != recipient_email):
+ new_answers.append(answer)
+ if (answer.last_edited_at
+ and answer.last_edited_at > last_reported_at
+ and answer.last_edited_by.email != recipient_email):
+ modified_answers.append(answer)
+ for comment in answer.comments.all():
+ if comment.added_at > last_reported_at and comment.user.email != recipient_email:
+ commented_answers.add(answer)
+ answer_comments.append(comment)
+
+ #create the report
+ if edited or new_answers or modified_answers or answer_comments:
+ out = []
+ if edited:
+ out.append(_('%(author)s modified the question') % {'author':self.last_edited_by.username})
+ if new_answers:
+ names = sets.Set(map(lambda x: x.author.username,new_answers))
+ people = ', '.join(names)
+ out.append(_('%(people)s posted %(new_answer_count)s new answers') \
+ % {'new_answer_count':len(new_answers),'people':people})
+ if comments:
+ names = sets.Set(map(lambda x: x.user.username,comments))
+ people = ', '.join(names)
+ out.append(_('%(people)s commented the question') % {'people':people})
+ if answer_comments:
+ names = sets.Set(map(lambda x: x.user.username,answer_comments))
+ people = ', '.join(names)
+ if len(commented_answers) > 1:
+ out.append(_('%(people)s commented answers') % {'people':people})
+ else:
+ out.append(_('%(people)s commented an answer') % {'people':people})
+ url = settings.APP_URL + self.get_absolute_url()
+ retval = '<a href="%s">%s</a>:<br>\n' % (url,self.title)
+ out = map(lambda x: '<li>' + x + '</li>',out)
+ retval += '<ul>' + '\n'.join(out) + '</ul><br>\n'
+ return retval
+ else:
+ return None
+
+ def __unicode__(self):
+ return self.title
+
+
+class QuestionView(models.Model):
+ question = models.ForeignKey(Question, related_name='viewed')
+ who = models.ForeignKey(User, related_name='question_views')
+ when = models.DateTimeField()
+
+ class Meta:
+ app_label = 'forum'
+
+class FavoriteQuestion(models.Model):
+ """A favorite Question of a User."""
+ question = models.ForeignKey(Question)
+ user = models.ForeignKey(User, related_name='user_favorite_questions')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+
+ class Meta:
+ app_label = 'forum'
+ db_table = u'favorite_question'
+ def __unicode__(self):
+ return '[%s] favorited at %s' %(self.user, self.added_at)
+
+class QuestionRevision(ContentRevision):
+ """A revision of a Question."""
+ question = models.ForeignKey(Question, related_name='revisions')
+ title = models.CharField(max_length=300)
+ tagnames = models.CharField(max_length=125)
+
+ class Meta(ContentRevision.Meta):
+ db_table = u'question_revision'
+ ordering = ('-revision',)
+
+ def get_question_title(self):
+ return self.question.title
+
+ def get_absolute_url(self):
+ #print 'in QuestionRevision.get_absolute_url()'
+ return reverse('question_revisions', args=[self.question.id])
+
+ def save(self, **kwargs):
+ """Looks up the next available revision number."""
+ if not self.revision:
+ self.revision = QuestionRevision.objects.filter(
+ question=self.question).values_list('revision',
+ flat=True)[0] + 1
+ super(QuestionRevision, self).save(**kwargs)
+
+ def __unicode__(self):
+ return u'revision %s of %s' % (self.revision, self.title)
+
+class AnonymousQuestion(AnonymousContent):
+ title = models.CharField(max_length=300)
+ tagnames = models.CharField(max_length=125)
+
+ def publish(self,user):
+ added_at = datetime.datetime.now()
+ QuestionManager.create_new(title=self.title, author=user, added_at=added_at,
+ wiki=self.wiki, tagnames=self.tagnames,
+ summary=self.summary, text=self.text)
+ self.delete()
+
+from answer import Answer, AnswerManager
diff --git a/forum/models/repute.py b/forum/models/repute.py
new file mode 100755
index 00000000..91ba0375
--- /dev/null
+++ b/forum/models/repute.py
@@ -0,0 +1,109 @@
+from base import *
+
+from django.utils.translation import ugettext as _
+
+class Badge(models.Model):
+ """Awarded for notable actions performed on the site by Users."""
+ GOLD = 1
+ SILVER = 2
+ BRONZE = 3
+ TYPE_CHOICES = (
+ (GOLD, _('gold')),
+ (SILVER, _('silver')),
+ (BRONZE, _('bronze')),
+ )
+
+ name = models.CharField(max_length=50)
+ type = models.SmallIntegerField(choices=TYPE_CHOICES)
+ slug = models.SlugField(max_length=50, blank=True)
+ description = models.CharField(max_length=300)
+ multiple = models.BooleanField(default=False)
+ # Denormalised data
+ awarded_count = models.PositiveIntegerField(default=0)
+
+ awarded_to = models.ManyToManyField(User, through='Award', related_name='badges')
+
+ class Meta:
+ app_label = 'forum'
+ db_table = u'badge'
+ ordering = ('name',)
+ unique_together = ('name', 'type')
+
+ def __unicode__(self):
+ return u'%s: %s' % (self.get_type_display(), self.name)
+
+ def save(self, **kwargs):
+ if not self.slug:
+ self.slug = self.name#slugify(self.name)
+ super(Badge, self).save(**kwargs)
+
+ def get_absolute_url(self):
+ return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
+
+class AwardManager(models.Manager):
+ def get_recent_awards(self):
+ awards = super(AwardManager, self).extra(
+ select={'badge_id': 'badge.id', 'badge_name':'badge.name',
+ 'badge_description': 'badge.description', 'badge_type': 'badge.type',
+ 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
+ },
+ tables=['award', 'badge', 'auth_user'],
+ order_by=['-awarded_at'],
+ where=['auth_user.id=award.user_id AND badge_id=badge.id'],
+ ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
+ return awards
+
+class Award(models.Model):
+ """The awarding of a Badge to a User."""
+ user = models.ForeignKey(User, related_name='award_user')
+ badge = models.ForeignKey('Badge', related_name='award_badge')
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ awarded_at = models.DateTimeField(default=datetime.datetime.now)
+ notified = models.BooleanField(default=False)
+
+ objects = AwardManager()
+
+ def __unicode__(self):
+ return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
+
+ class Meta:
+ app_label = 'forum'
+ db_table = u'award'
+
+class ReputeManager(models.Manager):
+ def get_reputation_by_upvoted_today(self, user):
+ """
+ For one user in one day, he can only earn rep till certain score (ep. +200)
+ by upvoted(also substracted from upvoted canceled). This is because we need
+ to prohibit gaming system by upvoting/cancel again and again.
+ """
+ if user is not None:
+ today = datetime.date.today()
+ sums = self.filter(models.Q(reputation_type=1) | models.Q(reputation_type=-8),
+ user=user, reputed_at__range=(today, today + datetime.timedelta(1))). \
+ agregate(models.Sum('positive'), models.SUM('negative'))
+
+ return sums['positive__sum'] + sums['negative__sum']
+ else:
+ return 0
+
+class Repute(models.Model):
+ """The reputation histories for user"""
+ user = models.ForeignKey(User)
+ positive = models.SmallIntegerField(default=0)
+ negative = models.SmallIntegerField(default=0)
+ question = models.ForeignKey('Question')
+ reputed_at = models.DateTimeField(default=datetime.datetime.now)
+ reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
+ reputation = models.IntegerField(default=1)
+
+ objects = ReputeManager()
+
+ def __unicode__(self):
+ return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at)
+
+ class Meta:
+ app_label = 'forum'
+ db_table = u'repute' \ No newline at end of file
diff --git a/forum/models/tag.py b/forum/models/tag.py
new file mode 100755
index 00000000..8d26d6f4
--- /dev/null
+++ b/forum/models/tag.py
@@ -0,0 +1,85 @@
+from base import *
+
+from django.utils.translation import ugettext as _
+
+class TagManager(models.Manager):
+ UPDATE_USED_COUNTS_QUERY = (
+ 'UPDATE tag '
+ 'SET used_count = ('
+ 'SELECT COUNT(*) FROM question_tags '
+ 'INNER JOIN question ON question_id=question.id '
+ 'WHERE tag_id = tag.id AND question.deleted=False'
+ ') '
+ 'WHERE id IN (%s)')
+
+ def get_valid_tags(self, page_size):
+ tags = self.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size]
+ return tags
+
+ def get_or_create_multiple(self, names, user):
+ """
+ Fetches a list of Tags with the given names, creating any Tags
+ which don't exist when necesssary.
+ """
+ tags = list(self.filter(name__in=names))
+ #Set all these tag visible
+ for tag in tags:
+ if tag.deleted:
+ tag.deleted = False
+ tag.deleted_by = None
+ tag.deleted_at = None
+ tag.save()
+
+ if len(tags) < len(names):
+ existing_names = set(tag.name for tag in tags)
+ new_names = [name for name in names if name not in existing_names]
+ tags.extend([self.create(name=name, created_by=user)
+ for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
+
+ return tags
+
+ def update_use_counts(self, tags):
+ """Updates the given Tags with their current use counts."""
+ if not tags:
+ return
+ cursor = connection.cursor()
+ query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
+ cursor.execute(query, [tag.id for tag in tags])
+ transaction.commit_unless_managed()
+
+ def get_tags_by_questions(self, questions):
+ question_ids = []
+ for question in questions:
+ question_ids.append(question.id)
+
+ question_ids_str = ','.join([str(id) for id in question_ids])
+ related_tags = self.extra(
+ tables=['tag', 'question_tags'],
+ where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"]
+ ).distinct()
+
+ return related_tags
+
+class Tag(DeletableContent):
+ name = models.CharField(max_length=255, unique=True)
+ created_by = models.ForeignKey(User, related_name='created_tags')
+ # Denormalised data
+ used_count = models.PositiveIntegerField(default=0)
+
+ objects = TagManager()
+
+ class Meta(DeletableContent.Meta):
+ db_table = u'tag'
+ ordering = ('-used_count', 'name')
+
+ def __unicode__(self):
+ return self.name
+
+class MarkedTag(models.Model):
+ TAG_MARK_REASONS = (('good',_('interesting')),('bad',_('ignored')))
+ tag = models.ForeignKey('Tag', related_name='user_selections')
+ user = models.ForeignKey(User, related_name='tag_selections')
+ reason = models.CharField(max_length=16, choices=TAG_MARK_REASONS)
+
+ class Meta:
+ app_label = 'forum' \ No newline at end of file
diff --git a/forum/models/user.py b/forum/models/user.py
new file mode 100755
index 00000000..ac6cbc0d
--- /dev/null
+++ b/forum/models/user.py
@@ -0,0 +1,67 @@
+from base import *
+
+from django.utils.translation import ugettext as _
+
+class Activity(models.Model):
+ """
+ We keep some history data for user activities
+ """
+ user = models.ForeignKey(User)
+ activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY)
+ active_at = models.DateTimeField(default=datetime.datetime.now)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ is_auditted = models.BooleanField(default=False)
+
+ def __unicode__(self):
+ return u'[%s] was active at %s' % (self.user.username, self.active_at)
+
+ class Meta:
+ app_label = 'forum'
+ db_table = u'activity'
+
+class EmailFeedSetting(models.Model):
+ DELTA_TABLE = {
+ 'w':datetime.timedelta(7),
+ 'd':datetime.timedelta(1),
+ 'n':datetime.timedelta(-1),
+ }
+ FEED_TYPES = (
+ ('q_all',_('Entire forum')),
+ ('q_ask',_('Questions that I asked')),
+ ('q_ans',_('Questions that I answered')),
+ ('q_sel',_('Individually selected questions')),
+ )
+ UPDATE_FREQUENCY = (
+ ('w',_('Weekly')),
+ ('d',_('Daily')),
+ ('n',_('No email')),
+ )
+ subscriber = models.ForeignKey(User)
+ feed_type = models.CharField(max_length=16,choices=FEED_TYPES)
+ frequency = models.CharField(max_length=8,choices=UPDATE_FREQUENCY,default='n')
+ added_at = models.DateTimeField(auto_now_add=True)
+ reported_at = models.DateTimeField(null=True)
+
+ def save(self,*args,**kwargs):
+ type = self.feed_type
+ subscriber = self.subscriber
+ similar = self.__class__.objects.filter(feed_type=type,subscriber=subscriber).exclude(pk=self.id)
+ if len(similar) > 0:
+ raise IntegrityError('email feed setting already exists')
+ super(EmailFeedSetting,self).save(*args,**kwargs)
+
+ class Meta:
+ app_label = 'forum'
+
+class AnonymousEmail(models.Model):
+ #validation key, if used
+ key = models.CharField(max_length=32)
+ email = models.EmailField(null=False,unique=True)
+ isvalid = models.BooleanField(default=False)
+
+ class Meta:
+ app_label = 'forum'
+
+
diff --git a/forum/modules.py b/forum/modules.py
new file mode 100755
index 00000000..26c4d50a
--- /dev/null
+++ b/forum/modules.py
@@ -0,0 +1,54 @@
+import os
+import types
+
+MODULES_PACKAGE = 'forum_modules'
+
+MODULES_FOLDER = os.path.join(os.path.dirname(__file__), '../' + MODULES_PACKAGE)
+
+MODULE_LIST = [
+ __import__('forum_modules.%s' % f, globals(), locals(), ['forum_modules'])
+ for f in os.listdir(MODULES_FOLDER)
+ if os.path.isdir(os.path.join(MODULES_FOLDER, f)) and
+ os.path.exists(os.path.join(MODULES_FOLDER, "%s/__init__.py" % f)) and
+ not os.path.exists(os.path.join(MODULES_FOLDER, "%s/DISABLED" % f))
+]
+
+def get_modules_script(script_name):
+ all = []
+
+ for m in MODULE_LIST:
+ try:
+ all.append(__import__('%s.%s' % (m.__name__, script_name), globals(), locals(), [m.__name__]))
+ except:
+ pass
+
+ return all
+
+def get_modules_script_classes(script_name, base_class):
+ scripts = get_modules_script(script_name)
+ all_classes = {}
+
+ for script in scripts:
+ all_classes.update(dict([
+ (n, c) for (n, c) in [(n, getattr(script, n)) for n in dir(script)]
+ if isinstance(c, (type, types.ClassType)) and issubclass(c, base_class)
+ ]))
+
+ return all_classes
+
+def get_all_handlers(name):
+ handler_files = get_modules_script('handlers')
+
+ return [
+ h for h in [
+ getattr(f, name) for f in handler_files
+ if hasattr(f, name)
+ ]
+
+ if callable(h)
+ ]
+
+def get_handler(name, default):
+ all = get_all_handlers(name)
+ print(len(all))
+ return len(all) and all[0] or default \ No newline at end of file
diff --git a/forum/skins/README b/forum/skins/README
index b2f32b77..5565fa83 100644
--- a/forum/skins/README
+++ b/forum/skins/README
@@ -4,14 +4,10 @@ this directory contains available skins
2) common - this directory is to media directory common to all or many templates
to create a new skin just create another directory under skins/
-and start populating it with templates named the same way as in
-default/templates
+and start populating it with the directory structure as in
+default/templates - templates must be named the same way
-all paths must be the same except they will be under
-yourtemplate/templates
-
-media does not have to be composed of files named the same way as in default skin
-whatever media you link to from your templates - will be in operation
+NO NEED TO CREATE ALL TEMPLATES/MEDIA FILES AT ONCE
templates are resolved in the following way:
* check in skin named as in settings.OSQA_DEFAULT_SKIN
@@ -21,3 +17,6 @@ media is resolved with one extra option
* settings.OSQA_DEFAULT_SKIN
* 'default'
* 'common'
+
+media does not have to be composed of files named the same way as in default skin
+whatever media you link to from your templates - will be in operation
diff --git a/forum/skins/default/media/js/com.cnprog.admin.js b/forum/skins/default/media/js/com.cnprog.admin.js
index 974dce23..39dff48c 100644
--- a/forum/skins/default/media/js/com.cnprog.admin.js
+++ b/forum/skins/default/media/js/com.cnprog.admin.js
@@ -3,11 +3,7 @@ $(document).ready( function(){
success: function(a,b){$('.admin #action_status').html($.i18n._('changes saved'));},
dataType:'json',
timeout:5000,
-<<<<<<< HEAD:templates/content/js/com.cnprog.admin.js
- url: $.i18n._('/') + $.i18n._('moderate-user/') + viewUserID + '/'
-=======
url: scriptUrl + $.i18n._('moderate-user/') + viewUserID + '/'
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.admin.js
};
var form = $('.admin #moderate_user_form').ajaxForm(options);
var box = $('.admin input#id_is_approved').click(function(){
diff --git a/forum/skins/default/media/js/com.cnprog.i18n.js b/forum/skins/default/media/js/com.cnprog.i18n.js
index 58cb8f16..da9bf396 100644
--- a/forum/skins/default/media/js/com.cnprog.i18n.js
+++ b/forum/skins/default/media/js/com.cnprog.i18n.js
@@ -59,10 +59,6 @@ var i18nZh = {
};
var i18nEn = {
-<<<<<<< HEAD:templates/content/js/com.cnprog.i18n.js
- '/':'/',
-=======
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.i18n.js
'need >15 points to report spam':'need >15 points to report spam ',
'>15 points requried to upvote':'>15 points required to upvote ',
'tags cannot be empty':'please enter at least one tag',
diff --git a/forum/skins/default/media/js/com.cnprog.utils.js b/forum/skins/default/media/js/com.cnprog.utils.js
index 93083288..3f7720c1 100644
--- a/forum/skins/default/media/js/com.cnprog.utils.js
+++ b/forum/skins/default/media/js/com.cnprog.utils.js
@@ -41,7 +41,7 @@ var notify = function() {
function appendLoader(containerSelector) {
$(containerSelector).append('<img class="ajax-loader" ' +
- 'src="mediaUrl("media/images/indicator.gif")" title="' +
+ 'src="' + mediaUrl("media/images/indicator.gif") + '" title="' +
$.i18n._('loading...') +
'" alt="' +
$.i18n._('loading...') +
diff --git a/forum/skins/default/media/js/wmd/wmd.js b/forum/skins/default/media/js/wmd/wmd.js
index 9888cda0..70271d4d 100644
--- a/forum/skins/default/media/js/wmd/wmd.js
+++ b/forum/skins/default/media/js/wmd/wmd.js
@@ -21,11 +21,11 @@ Attacklab.wmdBase = function(){
// Used to work around some browser bugs where we can't use feature testing.
- global.isIE = /msie/.test(nav.userAgent.toLowerCase());
- global.isIE_5or6 = /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase());
- global.isIE_7plus = global.isIE && !global.isIE_5or6;
- global.isOpera = /opera/.test(nav.userAgent.toLowerCase());
- global.isKonqueror = /konqueror/.test(nav.userAgent.toLowerCase());
+ global.isIE = /msie/.test(nav.userAgent.toLowerCase());
+ global.isIE_5or6 = /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase());
+ global.isIE_7plus = global.isIE && !global.isIE_5or6;
+ global.isOpera = /opera/.test(nav.userAgent.toLowerCase());
+ global.isKonqueror = /konqueror/.test(nav.userAgent.toLowerCase());
var toolbar_strong_label = $.i18n._('bold') + " <strong> Ctrl-B";
var toolbar_emphasis_label = $.i18n._('italic') + " <em> Ctrl-I";
@@ -52,9 +52,9 @@ Attacklab.wmdBase = function(){
var imageDialogText = "<p style='margin-top: 0px'>" + $.i18n._('enter image url') + '</p>';
var linkDialogText = "<p style='margin-top: 0px'>" + $.i18n._('enter url') + '</p>';
var uploadImageHTML ="<div>" + $.i18n._('upload image') + "</div>" +
- '<input type="file" name="file-upload" id="file-upload" size="26" '+
- 'onchange="return ajaxFileUpload($("#image-url"));"/><br>' +
- '<img id="loading" src="mediaUrl("media/images/indicator.gif")" style="display:none;"/>';
+ "<input type=\"file\" name=\"file-upload\" id=\"file-upload\" size=\"26\" "+
+ "onchange=\"return ajaxFileUpload($('#image-url'));\"/><br>" +
+ "<img id=\"loading\" src=\"" + mediaUrl("media/images/indicator.gif") + "\" style=\"display:none;\"/>";
// The default text that appears in the dialog input box when entering
// links.
@@ -115,7 +115,7 @@ Attacklab.wmdBase = function(){
}
else if (elem.currentStyle) {
// IE
- return elem.currentStyle["display"] !== "none";
+ return elem.currentStyle.display !== "none";
}
};
@@ -185,7 +185,7 @@ Attacklab.wmdBase = function(){
pattern = pre + pattern + post;
return new re(pattern, flags);
- }
+ };
// Sets the image for a button passed to the WMD editor.
@@ -218,8 +218,9 @@ Attacklab.wmdBase = function(){
var input; // The text box where you enter the hyperlink.
var type = 0;
// The dialog box type(0: Link, 1: Image)
- if(arguments.length == 4)
+ if(arguments.length == 4){
type = arguments[3];
+ }
if (defaultInputText === undefined) {
defaultInputText = "";
@@ -334,8 +335,9 @@ Attacklab.wmdBase = function(){
// The input text box
input = doc.createElement("input");
- if(type == 1)
+ if(type == 1){
input.id = "image-url";
+ }
input.type = "text";
input.value = defaultInputText;
style = input.style;
@@ -347,9 +349,9 @@ Attacklab.wmdBase = function(){
// The upload file input
if(type == 1){
var upload = doc.createElement("div");
- upload.innerHTML = uploadImageHTML;
- upload.style.padding = "5px";
- form.appendChild(upload);
+ upload.innerHTML = uploadImageHTML;
+ upload.style.padding = "5px";
+ form.appendChild(upload);
}
// The ok button
@@ -433,9 +435,10 @@ Attacklab.wmdBase = function(){
position.getTop = function(elem, isInner){
var result = elem.offsetTop;
if (!isInner) {
- while (elem = elem.offsetParent) {
- result += elem.offsetTop;
- }
+ while (elem.offsetParent) {
+ elem = elem.offsetParent;
+ result += elem.offsetTop;
+ }
}
return result;
};
@@ -762,7 +765,7 @@ Attacklab.wmdBase = function(){
var handlePaste = function(){
if (global.isIE || (inputStateObj && inputStateObj.text != wmd.panels.input.value)) {
- if (timer == undefined) {
+ if (timer === undefined) {
mode = "paste";
saveState();
refreshState();
@@ -923,17 +926,16 @@ Attacklab.wmdBase = function(){
}
doClick(this);
return false;
- }
+ };
}
}
else {
button.style.backgroundPosition = button.XShift + " " + disabledYShift;
button.onmouseover = button.onmouseout = button.onclick = function(){};
}
- }
-
+ };
+
var makeSpritedButtonRow = function(){
-
var buttonBar = document.getElementById("wmd-button-bar");
var normalYShift = "0px";
var disabledYShift = "-20px";
@@ -1102,7 +1104,7 @@ Attacklab.wmdBase = function(){
buttonRow.appendChild(helpButton);
*/
setUndoRedoButtonStates();
- }
+ };
var setupEditor = function(){
@@ -1298,7 +1300,7 @@ Attacklab.wmdBase = function(){
this.text = inputArea.value;
}
- }
+ };
// Sets the selected text in the input box after we've performed an
// operation.
@@ -1384,7 +1386,7 @@ Attacklab.wmdBase = function(){
// Restore this state into the input area.
this.restore = function(){
- if (stateObj.text != undefined && stateObj.text != inputArea.value) {
+ if (stateObj.text !== undefined && stateObj.text != inputArea.value) {
inputArea.value = stateObj.text;
}
this.setInputAreaSelection();
@@ -1903,12 +1905,10 @@ Attacklab.wmdBase = function(){
if (wmd.panels.preview) {
wmd.panels.preview.scrollTop = (wmd.panels.preview.scrollHeight - wmd.panels.preview.clientHeight) * getScaleFactor(wmd.panels.preview);
- ;
}
if (wmd.panels.output) {
wmd.panels.output.scrollTop = (wmd.panels.output.scrollHeight - wmd.panels.output.clientHeight) * getScaleFactor(wmd.panels.output);
- ;
}
};
diff --git a/forum/skins/default/media/style/style.css b/forum/skins/default/media/style/style.css
index 88c7e7db..02148ab0 100644
--- a/forum/skins/default/media/style/style.css
+++ b/forum/skins/default/media/style/style.css
@@ -1,656 +1,1340 @@
@import url(jquery.autocomplete.css);
@import url(openid.css);
@import url(prettify.css);
+
/* 公用 */
-body{background:#FFF; font-size:12px; line-height:150%; margin:0; padding:0; color:#000; font-family: sans-serif;
-}
-div{margin:0 auto; padding:0;}
-h1,h2,h3,h4,h5,h6,ul,li,dl,dt,dd,form,img,p{margin:0; padding:0; border:none; }
-label {vertical-align:middle;}
-hr {border:none;border-top: 1px dashed #ccccce;}
-input, select {vertical-align:middle;font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;}
-p{margin-bottom:13px; font-size:13px; line-height:140%;}
-a {color:#333333; text-decoration:none;}
-.badges a {color:#763333;text-decoration:underline;}
-a:hover {text-decoration:underline;}
-.block{width:960px; height:auto;}
-.fleft{float:left;}
-.fright{float:right;}
-.tleft{text-align:left;}
-.tcenter{text-align:center;}
-.tright{text-align:right;}
-.dis{display:block;}
-.inline{display:inline;}
-.none{display:none;}
-.red{color:#CC0000;}
-.b{font-weight:bold;}
-.f10{font-size:10px;}
-.f11{font-size:11px;}
-.f12{font-size:12px;}
-.f13{font-size:13px;}
-.f14{font-size:14px;}
-.white{color:#FFFFFF;}
-.u{text-decoration:underline;}
-.spacer1{height:6px; line-height:6px; clear:both; visibility:hidden;}
-.spacer3{height:30px; line-height:30px; clear:both; visibility:hidden;}
-h1{font-size:160%;padding:5px 0 5px 0;}
-h2{font-size:140%;padding:3px 0 3px 0;}
-h3{font-size:120%;padding:3px 0 3px 0;}
-ul
-{
- list-style: disc;
- margin-left: 20px;
- padding-left:0px;
- margin-bottom: 1em;
-}
-ol
-{
- list-style: decimal;
- margin-left: 30px;
- margin-bottom: 1em;
- padding-left:0px;
+body {
+ background: #FFF;
+ font-size: 12px;
+ line-height: 150%;
+ margin: 0;
+ padding: 0;
+ color: #000;
+ font-family: sans-serif;
+}
+
+div {
+ margin: 0 auto;
+ padding: 0;
+}
+
+h1, h2, h3, h4, h5, h6, ul, li, dl, dt, dd, form, img, p {
+ margin: 0;
+ padding: 0;
+ border: none;
+}
+
+label {
+ vertical-align: middle;
+}
+
+hr {
+ border: none;
+ border-top: 1px dashed #ccccce;
+}
+
+input, select {
+ vertical-align: middle;
+ font-family: Trebuchet MS, "segoe ui", Helvetica, "Microsoft YaHei", 宋 体, Tahoma, Verdana, MingLiu, PMingLiu, Arial, sans-serif;
+}
+
+p {
+ margin-bottom: 13px;
+ font-size: 13px;
+ line-height: 140%;
+}
+
+a {
+ color: #333333;
+ text-decoration: none;
+}
+
+.badges a {
+ color: #763333;
+ text-decoration: underline;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+.block {
+ width: 960px;
+ height: auto;
+}
+
+.fleft {
+ float: left;
+}
+
+.fright {
+ float: right;
+}
+
+.tleft {
+ text-align: left;
+}
+
+.tcenter {
+ text-align: center;
+}
+
+.tright {
+ text-align: right;
+}
+
+.dis {
+ display: block;
+}
+
+.inline {
+ display: inline;
+}
+
+.none {
+ display: none;
+}
+
+.red {
+ color: #CC0000;
+}
+
+.b {
+ font-weight: bold;
+}
+
+.f10 {
+ font-size: 10px;
+}
+
+.f11 {
+ font-size: 11px;
+}
+
+.f12 {
+ font-size: 12px;
+}
+
+.f13 {
+ font-size: 13px;
+}
+
+.f14 {
+ font-size: 14px;
+}
+
+.white {
+ color: #FFFFFF;
+}
+
+.u {
+ text-decoration: underline;
+}
+
+.spacer1 {
+ height: 6px;
+ line-height: 6px;
+ clear: both;
+ visibility: hidden;
+}
+
+.spacer3 {
+ height: 30px;
+ line-height: 30px;
+ clear: both;
+ visibility: hidden;
+}
+
+h1 {
+ font-size: 160%;
+ padding: 5px 0 5px 0;
+}
+
+h2 {
+ font-size: 140%;
+ padding: 3px 0 3px 0;
}
+
+h3 {
+ font-size: 120%;
+ padding: 3px 0 3px 0;
+}
+
+ul {
+ list-style: disc;
+ margin-left: 20px;
+ padding-left: 0px;
+ margin-bottom: 1em;
+}
+
+ol {
+ list-style: decimal;
+ margin-left: 30px;
+ margin-bottom: 1em;
+ padding-left: 0px;
+}
+
td ul {
- vertical-align:middle;
+ vertical-align: middle;
}
+
li input {
margin: 3px 3px 4px 3px;
}
-pre
-{
- font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
- font-size:100%;
- margin-bottom: 10px;
- overflow: auto;
- width: 580px;
- background-color: #F5F5F5;
- padding-left:5px;
- padding-top:5px;
- padding-bottom: 20px !ie7;
+
+pre {
+ font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
+ font-size: 100%;
+ margin-bottom: 10px;
+ overflow: auto;
+ width: 580px;
+ background-color: #F5F5F5;
+ padding-left: 5px;
+ padding-top: 5px;
+ padding-bottom: 20px ! ie7;
}
-code{
+code {
font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
- font-size:100%;
+ font-size: 100%;
}
-blockquote
-{
- margin-bottom: 10px;
- margin-right: 15px;
- padding: 10px 0px 1px 10px;
- background-color: #F5F5F5;
+blockquote {
+ margin-bottom: 10px;
+ margin-right: 15px;
+ padding: 10px 0px 1px 10px;
+ background-color: #F5F5F5;
}
/*页面布局*/
-#wrapper {width:960px;margin:auto;padding:0;}
+#wrapper {
+ width: 960px;
+ margin: auto;
+ padding: 0;
+}
+
#roof {
- position:relative;
- margin-top:0px;
- background:#FFF;
-}
-#room {padding:10px 0 10px 0;background-color:#FFF;border-bottom:1px solid #777;}
-#CALeft{width:710px; float:left; position:relative;}
-#CARight{width:240px; float:right;}
-#CAFull{float:left;padding:0 5px 0 5px;width:950px;}
-#ground {width:100%;border-top:1px solid #000; padding-top:6px; padding-bottom:0px; text-align:center;background:#777;}
+ position: relative;
+ margin-top: 0px;
+ background: #FFF;
+}
+
+#room {
+ padding: 10px 0 10px 0;
+ background-color: #FFF;
+ border-bottom: 1px solid #777;
+}
+
+#CALeft {
+ width: 710px;
+ float: left;
+ position: relative;
+}
+
+#CARight {
+ width: 240px;
+ float: right;
+}
+
+#CAFull {
+ float: left;
+ padding: 0 5px 0 5px;
+ width: 950px;
+}
+
+#ground {
+ width: 100%;
+ border-top: 1px solid #000;
+ padding-top: 6px;
+ padding-bottom: 0px;
+ text-align: center;
+ background: #777;
+}
+
/*#licenseLogo {position:absolute;top:10px;right:10px;}*/
/*顶部及导航栏*/
#top {
- position:absolute;
- top:0px;
- right:0px;
- height:20px;
- text-align:right;
- padding:3px;
- background-color:#ffffff;
- width:500px;
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ height: 20px;
+ text-align: right;
+ padding: 3px;
+ background-color: #ffffff;
+ width: 500px;
}
+
/*#header {width:960px;}*/
-#top a {height:35px; text-align:right;
- /*letter-spacing:1px; */
- margin-left:20px;text-decoration:underline; font-size:12px; color:#333333;}
+#top a {
+ height: 35px;
+ text-align: right; /*letter-spacing:1px; */
+ margin-left: 20px;
+ text-decoration: underline;
+ font-size: 12px;
+ color: #333333;
+}
+
#logo {
- padding: 5px 0px 0px 0px;
-}
-#navBar {float:clear;position:relative;display:block;width:960px;}
-#navBar .nav {margin:20px 0px 0px 16px;
- /*letter-spacing:1px; */
- }
-#navBar .nav a {color:#333333; background-color:#fff0e0;
- /*border-left: 1px solid #eeeeec;
- border-right: 1px solid #babdb6;
- border-top: 1px solid #eeeeec;*/
- border: 1px solid #888888;
- border-bottom: none;
- padding:0px 12px 3px 12px; height:25px; line-height:30px;margin-left:10px; font-size:14px; font-weight:400; text-decoration:none;display: block;float: left;}
-#navBar .nav a:hover {text-decoration:underline}
-#navBar .nav a.on {height:24px;line-height:28px;
- border-bottom: 1px solid #a40000;
- border-right:1px solid #820000;
- border-top:1px solid #d40000;
- border-left:1px solid #d40000;
- /*background:#A31E39; */
- background:#a40000;
- color:#FFF; font-weight:600; text-decoration:none}
-#navBar .nav a.special {font-size:14px; color:#B02B2C; font-weight:bold; text-decoration:none; }
-#navBar .nav a.special:hover {text-decoration:underline;}
-#navBar .nav div.focus {float:right; padding-right:0px;}
+ padding: 5px 0px 0px 0px;
+}
+
+#navBar {
+ float: clear;
+ position: relative;
+ display: block;
+ width: 960px;
+}
+
+#navBar .nav {
+ margin: 20px 0px 0px 16px; /*letter-spacing:1px; */
+}
+
+#navBar .nav a {
+ color: #333333;
+ background-color: #fff0e0;
+ /*border-left: 1px solid #eeeeec;
+ border-right: 1px solid #babdb6;
+ border-top: 1px solid #eeeeec;*/
+ border: 1px solid #888888;
+ border-bottom: none;
+ padding: 0px 12px 3px 12px;
+ height: 25px;
+ line-height: 30px;
+ margin-left: 10px;
+ font-size: 14px;
+ font-weight: 400;
+ text-decoration: none;
+ display: block;
+ float: left;
+}
+
+#navBar .nav a:hover {
+ text-decoration: underline
+}
+
+#navBar .nav a.on {
+ height: 24px;
+ line-height: 28px;
+ border-bottom: 1px solid #a40000;
+ border-right: 1px solid #820000;
+ border-top: 1px solid #d40000;
+ border-left: 1px solid #d40000; /*background:#A31E39; */
+ background: #a40000;
+ color: #FFF;
+ font-weight: 600;
+ text-decoration: none
+}
+
+#navBar .nav a.special {
+ font-size: 14px;
+ color: #B02B2C;
+ font-weight: bold;
+ text-decoration: none;
+}
+
+#navBar .nav a.special:hover {
+ text-decoration: underline;
+}
+
+#navBar .nav div.focus {
+ float: right;
+ padding-right: 0px;
+}
+
/*搜索栏*/
-#searchBar {width:958px;
- background-color:#888a85;/*#e9b96e;*/
+#searchBar {
+ width: 958px;
+ background-color: #888a85; /*#e9b96e;*/
border: 1px solid #aaaaaa;
- padding:4px 0 0 0;} /* #B02B2C */
-#searchBar .content { }
-#searchBar .searchInput {font-size:13px; height:18px; width:400px;}
-#searchBar .searchBtn {font-size:14px; height:26px; width:80px;}
-#searchBar .options {padding:3px 0 3px 0;font-size:100%;color:#EEE;
- /*letter-spacing:1px;*/
- }
-#searchBar .options INPUT {margin:0 3px 0 15px;}
-#searchBar .options INPUT:hover {cursor:pointer}
+ padding: 4px 0 0 0;
+}
+
+/* #B02B2C */
+#searchBar .content {
+}
+
+#searchBar .searchInput {
+ font-size: 13px;
+ height: 18px;
+ width: 400px;
+}
+
+#searchBar .searchBtn {
+ font-size: 14px;
+ height: 26px;
+ width: 80px;
+}
+
+#searchBar .options {
+ padding: 3px 0 3px 0;
+ font-size: 100%;
+ color: #EEE; /*letter-spacing:1px;*/
+}
+
+#searchBar .options INPUT {
+ margin: 0 3px 0 15px;
+}
+
+#searchBar .options INPUT:hover {
+ cursor: pointer
+}
/*问题列表*/
-#listA {float:left; background-color:#FFF;padding:0 0px 0 0px; width:100%;}
+#listA {
+ float: left;
+ background-color: #FFF;
+ padding: 0 0px 0 0px;
+ width: 100%;
+}
+
#listA .qstA {
- position:relative;
- padding:3px 5px 5px 10px;
- border-top:1px dashed #ccccce;
+ position: relative;
+ padding: 3px 5px 5px 10px;
+ border-top: 1px dashed #ccccce;
/*border-left:1px solid #ebebbe;
border-right:1px solid #b4b48e;
border-bottom:1px solid #b4b48e;*/
- background: white;/* #f9f7ed;*/
- /*margin:10px 0 10px 0;*/
- /*background:url(../images/quest-bg.gif) repeat-x top;*/
+ background: white; /* #f9f7ed;*/
+/*margin:10px 0 10px 0;*/
+/*background:url(../images/quest-bg.gif) repeat-x top;*/
}
-#listA .qstA thumb {float:left; }
-#listA .qstA H2 {font-size:14px; font-weight:800; margin:8px auto;padding:0px;}
-#listA .qstA H2 a {color:333333/*#2e3436*/;font-size:15px;}
+
+#listA .qstA thumb {
+ float: left;
+}
+
+#listA .qstA H2 {
+ font-size: 14px;
+ font-weight: 800;
+ margin: 8px auto;
+ padding: 0px;
+}
+
+#listA .qstA H2 a {
+ color: 333333 /*#2e3436*/;
+ font-size: 15px;
+}
+
#listA .qstA .stat {
- position:absolute;
- right:0px;
- bottom:5px;
- font-size:12px;
- /*letter-spacing:1px;*/
- float:right;
-}
-#listA .qstA .stat span {margin-right:5px;}
-#listA .qstA .stat td {min-width:40px;text-align:center;}
+ position: absolute;
+ right: 0px;
+ bottom: 5px;
+ font-size: 12px; /*letter-spacing:1px;*/
+ float: right;
+}
+
+#listA .qstA .stat span {
+ margin-right: 5px;
+}
+
+#listA .qstA .stat td {
+ min-width: 40px;
+ text-align: center;
+}
+
#listA .qstA .stat .num {
- font-family:sans-serif;
- color:#a40000;
+ font-family: sans-serif;
+ color: #a40000;
/*background:#eeeeec;
- border: 1px solid #babdb6;*/
- margin:0px;
- font-size:17px;
- font-weight:800;
-}
-#listA .qstA table {border-spacing:0px;}
-#listA .qstA table td {padding:0px;width:60px;text-align:center;}
-#listA .qstA .stat .unit {color:#777777;margin:0px}
-#listA .qstA .from {margin-top:5px; font-size:13px;color:#777777;}
-#listA .qstA .from .score {font-family:sans-serif;color:#555555;}
-#listA .qstA .date {margin-left:10px; color:#777777;}
-#listA .qstA .wiki {color:#763333;font-size:12px;}
-#listA .qstA .from a {}
-#listA .qstA .from IMG {vertical-align:middle;}
-#listA .qstA .author {font-weight:400; }
-#listA .qstA .author a{color:#444444; }
-#listA .qstA .summary{margin-right:5px;}
+ border: 1px solid #babdb6;*/
+ margin: 0px;
+ font-size: 17px;
+ font-weight: 800;
+}
+
+#listA .qstA table {
+ border-spacing: 0px;
+}
+
+#listA .qstA table td {
+ padding: 0px;
+ width: 60px;
+ text-align: center;
+}
+
+#listA .qstA .stat .unit {
+ color: #777777;
+ margin: 0px
+}
+
+#listA .qstA .from {
+ margin-top: 5px;
+ font-size: 13px;
+ color: #777777;
+}
+
+#listA .qstA .from .score {
+ font-family: sans-serif;
+ color: #555555;
+}
+
+#listA .qstA .date {
+ margin-left: 10px;
+ color: #777777;
+}
+
+#listA .qstA .wiki {
+ color: #763333;
+ font-size: 12px;
+}
+
+#listA .qstA .from a {
+}
+
+#listA .qstA .from IMG {
+ vertical-align: middle;
+}
+
+#listA .qstA .author {
+ font-weight: 400;
+}
+
+#listA .qstA .author a {
+ color: #444444;
+}
+
+#listA .qstA .summary {
+ margin-right: 5px;
+}
+
+.short-summary {
+ position: relative;
+ padding: 3px 5px 5px 10px;
+ border-top: 1px dotted #ccccce;
+ overflow: hidden;
+ width: 700px;
+ float: left;
+}
+
+.short-summary h2 {
+ font-size: 16px;
+ font-family: "Trebuchet MS", "segoe ui", arial, sans-serif;
+}
+
+.short-summary .userinfo {
+ float: right;
+ margin-top: 8px;
+}
+
+.short-summary .counts {
+ float: right;
+ margin-top: 4px;
+}
+
+.short-summary .counts .item-count {
+ font-size: 17px;
+ font-weight: 800;
+}
+
+.short-summary .votes,
+.short-summary .status,
+.short-summary .views {
+ font-size: 12px;
+ text-align: center;
+ margin: 0 0 0 7px;
+ padding: 4px 2px 0px 2px;
+ width: 46px;
+ height: 48px;
+ float: left;
+ -moz-border-radius: 5px;
+ -khtml-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
#question-table {
- margin-bottom:10px;
- /*border-bottom:1px solid #888a85;*/
+ margin-bottom: 10px; /*border-bottom:1px solid #888a85;*/
}
-.evenMore {font-size:14px; font-weight:800;}
-.questions-count{
- font-size:32px;
- font-family:sans-serif;
- font-weight:600;
- padding:0 0 5px 0px;
- color:#a40000;
- margin-top:3px;
+
+.evenMore {
+ font-size: 14px;
+ font-weight: 800;
+}
+
+.questions-count {
+ font-size: 32px;
+ font-family: sans-serif;
+ font-weight: 600;
+ padding: 0 0 5px 0px;
+ color: #a40000;
+ margin-top: 3px;
}
/*内容块*/
-.boxA {background:#888a85; padding:6px; margin-bottom:8px;border 1px solid #babdb6;}
-.boxA H3 {font-size:13px; font-weight:800; color:#FFF; margin:0; padding:0; margin-bottom:4px;}
-.boxA .body {border:1px solid #999; padding:8px; background:#FFF; font-size:13px;}
-.boxA .more {padding:2px; text-align:right; font-weight:800;}
-.boxB {background:#F9F7ED; padding:6px; margin-bottom:8px;border:solid 1px #aaaaaa;}
-.boxB H3 {font-size:13px; font-weight:800; color:#000; margin:0; padding:0 0 0 15px; margin-bottom:4px; background:url(../images/dot-g.gif) no-repeat left center;}
-.boxB .body {border:1px solid #aaaaaa; padding:8px; background:#FFF; font-size:13px; line-height:160%;}
-.boxB .more {padding:1px; text-align:right; font-weight:800;}
+.boxA {
+ background: #888a85;
+ padding: 6px;
+ margin-bottom: 8px;
+ border 1px solid #babdb6;
+}
+
+.boxA H3 {
+ font-size: 13px;
+ font-weight: 800;
+ color: #FFF;
+ margin: 0;
+ padding: 0;
+ margin-bottom: 4px;
+}
+
+.boxA .body {
+ border: 1px solid #999;
+ padding: 8px;
+ background: #FFF;
+ font-size: 13px;
+}
+
+.boxA .more {
+ padding: 2px;
+ text-align: right;
+ font-weight: 800;
+}
+
+.boxB {
+ background: #F9F7ED;
+ padding: 6px;
+ margin-bottom: 8px;
+ border: solid 1px #aaaaaa;
+}
+
+.boxB H3 {
+ font-size: 13px;
+ font-weight: 800;
+ color: #000;
+ margin: 0;
+ padding: 0 0 0 15px;
+ margin-bottom: 4px;
+ background: url(../images/dot-g.gif) no-repeat left center;
+}
+
+.boxB .body {
+ border: 1px solid #aaaaaa;
+ padding: 8px;
+ background: #FFF;
+ font-size: 13px;
+ line-height: 160%;
+}
+
+.boxB .more {
+ padding: 1px;
+ text-align: right;
+ font-weight: 800;
+}
+
.boxC {
- background: #cacdc6;/*f9f7ed;*/
- padding:10px;
- margin-bottom:8px;
- border-top:1px solid #eeeeec;
- border-left:1px solid #eeeeec;
- border-right:1px solid #a9aca5;
- border-bottom:1px solid #babdb6;
+ background: #cacdc6; /*f9f7ed;*/
+ padding: 10px;
+ margin-bottom: 8px;
+ border-top: 1px solid #eeeeec;
+ border-left: 1px solid #eeeeec;
+ border-right: 1px solid #a9aca5;
+ border-bottom: 1px solid #babdb6;
}
+
.boxC p {
- margin-bottom:8px;
+ margin-bottom: 8px;
}
+
.boxC p.nomargin {
- margin:0px;
+ margin: 0px;
}
+
.boxC p.info-box-follow-up-links {
- text-align:right;
- margin:0;
+ text-align: right;
+ margin: 0;
}
+
/*分页*/
-.pager {margin-top:10px; margin-bottom:16px; float:left;}
-.pagesize {margin-top:10px; margin-bottom:16px; float:right;}
+.pager {
+ margin-top: 10px;
+ margin-bottom: 16px;
+ float: left;
+}
+
+.pagesize {
+ margin-top: 10px;
+ margin-bottom: 16px;
+ float: right;
+}
/** PAGINATOR **/
.paginator {
- padding:5px 0 10px 0;
- font:normal 12px sans-serif;
+ padding: 5px 0 10px 0;
+ font: normal 12px sans-serif;
}
.paginator .prev-na,
.paginator .next-na {
- padding:.3em;
- font:bold .875em sans-serif;
+ padding: .3em;
+ font: bold .875em sans-serif;
}
.paginator .prev-na,
.paginator .next-na {
- border:1px solid #ccc;
- background-color:#f9f9f9;
- color:#aaa;
- font-weight:normal;
+ border: 1px solid #ccc;
+ background-color: #f9f9f9;
+ color: #aaa;
+ font-weight: normal;
}
.paginator .prev a, .paginator .prev a:visited,
.paginator .next a, .paginator .next a:visited {
- border:1px solid #fff;
- background-color:#fff;
- color:#777;
- padding:2px 4px 3px 4px;
- font:bold 100% sans-serif;
+ border: 1px solid #fff;
+ background-color: #fff;
+ color: #777;
+ padding: 2px 4px 3px 4px;
+ font: bold 100% sans-serif;
}
-.paginator .prev, .paginator .prev-na { margin-right:.5em; }
-.paginator .next, .paginator .next-na { margin-left:.5em; }
+.paginator .prev, .paginator .prev-na {
+ margin-right: .5em;
+}
+
+.paginator .next, .paginator .next-na {
+ margin-left: .5em;
+}
.paginator .page a, .paginator .page a:visited, .paginator .curr {
- padding:.25em;
- font:normal .875em verdana;
- border:1px solid #ccc;
- background-color:#fff;
- margin:0em .25em;
- color:#777;
+ padding: .25em;
+ font: normal .875em verdana;
+ border: 1px solid #ccc;
+ background-color: #fff;
+ margin: 0em .25em;
+ color: #777;
}
.paginator .curr {
- background-color:#777;
- color:#fff;
- border:1px solid #777;
- font-weight:bold;
+ background-color: #777;
+ color: #fff;
+ border: 1px solid #777;
+ font-weight: bold;
}
.paginator .page a:hover,
.paginator .curr a:hover,
.paginator .prev a:hover,
.paginator .next a:hover {
- color:#fff;
- background-color:#777;
- border:1px solid #777;
- text-decoration:none;
+ color: #fff;
+ background-color: #777;
+ border: 1px solid #777;
+ text-decoration: none;
}
-.paginator .text{
- color:#777;
- padding:.3em;
- font:bold 100% sans-serif;
+.paginator .text {
+ color: #777;
+ padding: .3em;
+ font: bold 100% sans-serif;
}
-.paginator-container{
- float:right;
- padding:10px 0 10px 0;
+.paginator-container {
+ float: right;
+ padding: 10px 0 10px 0;
}
-.paginator-container-left{
- padding:5px 0 10px 0;
+.paginator-container-left {
+ padding: 5px 0 10px 0;
}
/*标签*/
-.tag {font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
-.tags {font-family:sans-serif; line-height:200%; display:block; margin-top:5px;}
-.tags a {white-space: nowrap; font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
-.tags a:hover {background-color:#fFF;color:#333;}
-.tagsbox {line-height:200%;}
-.tagsbox a {font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
-.tagsbox a:hover {background-color:#fFF;color:#333;}
-.tag-number {font-weight:700;font-family:sans-serif;}
-.marked-tags { margin-top: 0px;margin-bottom: 5px; }
-.deletable-tag { margin-right: 3px; white-space:nowrap; }
+.tag {
+ font-size: 13px;
+ font-weight: normal;
+ color: #333;
+ text-decoration: none;
+ background-color: #EEE;
+ border-left: 3px solid #777;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid #CCC;
+ border-right: 1px solid #CCC;
+ padding: 1px 8px 1px 8px;
+}
+
+.tags {
+ font-family: sans-serif;
+ line-height: 200%;
+ display: block;
+ margin-top: 5px;
+}
+
+.tags a {
+ white-space: nowrap;
+ font-size: 13px;
+ font-weight: normal;
+ color: #333;
+ text-decoration: none;
+ background-color: #EEE;
+ border-left: 3px solid #777;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid #CCC;
+ border-right: 1px solid #CCC;
+ padding: 1px 8px 1px 8px;
+}
+
+.tags a:hover {
+ background-color: #fFF;
+ color: #333;
+}
+
+.tagsbox {
+ line-height: 200%;
+}
+
+.tagsbox a {
+ font-size: 13px;
+ font-weight: normal;
+ color: #333;
+ text-decoration: none;
+ background-color: #EEE;
+ border-left: 3px solid #777;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid #CCC;
+ border-right: 1px solid #CCC;
+ padding: 1px 8px 1px 8px;
+}
+
+.tagsbox a:hover {
+ background-color: #fFF;
+ color: #333;
+}
+
+.tag-number {
+ font-weight: 700;
+ font-family: sans-serif;
+}
+
+.marked-tags {
+ margin-top: 0px;
+ margin-bottom: 5px;
+}
+
+.deletable-tag {
+ margin-right: 3px;
+ white-space: nowrap;
+}
/*奖牌*/
-a.medal { font-size:14px; line-height:250%; font-weight:800; color:#333; text-decoration:none; background:url(../images/medala.gif) no-repeat; border-left:1px solid #EEE; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:4px 12px 4px 6px;}
-a:hover.medal {color:#333; text-decoration:none; background:url(../images/medala_on.gif) no-repeat; border-left:1px solid #E7E296; border-top:1px solid #E7E296; border-bottom:1px solid #D1CA3D; border-right:1px solid #D1CA3D;}
+a.medal {
+ font-size: 14px;
+ line-height: 250%;
+ font-weight: 800;
+ color: #333;
+ text-decoration: none;
+ background: url(../images/medala.gif) no-repeat;
+ border-left: 1px solid #EEE;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid #CCC;
+ border-right: 1px solid #CCC;
+ padding: 4px 12px 4px 6px;
+}
+
+a:hover.medal {
+ color: #333;
+ text-decoration: none;
+ background: url(../images/medala_on.gif) no-repeat;
+ border-left: 1px solid #E7E296;
+ border-top: 1px solid #E7E296;
+ border-bottom: 1px solid #D1CA3D;
+ border-right: 1px solid #D1CA3D;
+}
/*Tab栏*/
-.tabBar{background-color:#FFF;border-bottom: 1px solid white;height: 30px; width: 100%;clear:both; margin-bottom:3px;}
-.tabsA {background-color:#FFF;float:right;position:relative;display:block;font-weight:bold;height:20px;}
-.tabsB {background-color:#FFF;float:left;position:relative;display:block;font-weight:bold;height:20px;}
-.tabsA a.on, .tabsA a:hover,.tabsB a.on, .tabsB a:hover {
+.tabBar {
+ background-color: #FFF;
+ border-bottom: 1px solid white;
+ height: 30px;
+ width: 100%;
+ clear: both;
+ margin-bottom: 3px;
+}
+
+.tabsA {
+ background-color: #FFF;
+ float: right;
+ position: relative;
+ display: block;
+ font-weight: bold;
+ height: 20px;
+}
+
+.tabsB {
+ background-color: #FFF;
+ float: left;
+ position: relative;
+ display: block;
+ font-weight: bold;
+ height: 20px;
+}
+
+.tabsA a.on, .tabsA a:hover, .tabsB a.on, .tabsB a:hover {
background: #fff;
- color:#a40000;
- border-top:1px solid #babdb6;
- border-left:1px solid #babdb6;
- border-right:1px solid #888a85;
- border-bottom:1px solid #888a85;
- height: 24px;
- line-height: 26px;
- margin-top: 3px;
- padding: 0px 11px 0px 11px;}
+ color: #a40000;
+ border-top: 1px solid #babdb6;
+ border-left: 1px solid #babdb6;
+ border-right: 1px solid #888a85;
+ border-bottom: 1px solid #888a85;
+ height: 24px;
+ line-height: 26px;
+ margin-top: 3px;
+ padding: 0px 11px 0px 11px;
+}
.tabsA a {
background: #f9f7eb;
- border-top:1px solid #eeeeec;
- border-left:1px solid #eeeeec;
- border-right:1px solid #a9aca5;
- border-bottom:1px solid #888a85;
- color: #888a85;
- display: block;
- float: left;
- height: 20px;
- line-height: 22px;
- margin: 5px 4px 0 0;
- padding: 0 11px 0 11px;
- text-decoration: none;
-}
-.tabsB a {background: #eee;
- border: 1px solid #eee;
- color: #777;
- display: block;
- float: left;
- height: 22px;
- line-height: 28px;
- margin: 5px 0px 0 4px;
- padding: 0 11px 0 11px;
- text-decoration: none;
+ border-top: 1px solid #eeeeec;
+ border-left: 1px solid #eeeeec;
+ border-right: 1px solid #a9aca5;
+ border-bottom: 1px solid #888a85;
+ color: #888a85;
+ display: block;
+ float: left;
+ height: 20px;
+ line-height: 22px;
+ margin: 5px 4px 0 0;
+ padding: 0 11px 0 11px;
+ text-decoration: none;
+}
+
+.tabsB a {
+ background: #eee;
+ border: 1px solid #eee;
+ color: #777;
+ display: block;
+ float: left;
+ height: 22px;
+ line-height: 28px;
+ margin: 5px 0px 0 4px;
+ padding: 0 11px 0 11px;
+ text-decoration: none;
}
+
/*.tabsA a:hover, .tabsB a:hover {background: #fff;border: 1px solid #777;border-bottom:3px solid #FFF;}*/
-.headlineA {font-size:13px; border-bottom:1px solid #777; padding-bottom:2px; font-weight:800; margin-bottom:12px; text-align:right; height:30px;}
-.headQuestions {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(../images/dot-list.gif) no-repeat left center;}
-.headAnswers {float:left; padding:3px; font-size:18px; font-weight:800; background:url(../images/ico_answers.gif) left 2px no-repeat; padding-left:24px;}
-.headTags {float:left; padding:3px; font-size:18px; font-weight:800; background:url(../images/ico_tags.gif) no-repeat; padding-left:24px;}
-.headUsers {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(../images/dot-list.gif) no-repeat left center;}
-.headMedals {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(../images/dot-list.gif) no-repeat left center;}
-.headLogin {float:left; padding:3px; font-size:15px; font-weight:800; background:url(../images/ico_login.gif) no-repeat; padding-left:24px;}
+.headlineA {
+ font-size: 13px;
+ border-bottom: 1px solid #777;
+ padding-bottom: 2px;
+ font-weight: 800;
+ margin-bottom: 12px;
+ text-align: right;
+ height: 30px;
+}
+
+.headQuestions {
+ float: left;
+ height: 23px;
+ line-height: 23px;
+ margin: 5px 0 0 5px;
+ padding: 0px 6px 0px 15px;
+ font-size: 15px;
+ font-weight: 700;
+ border-bottom: 0px solid #777;
+ border-left: 0px solid #darkred;
+ background-color: #FFF;
+ background: url(../images/dot-list.gif) no-repeat left center;
+}
+
+.headAnswers {
+ float: left;
+ padding: 3px;
+ font-size: 18px;
+ font-weight: 800;
+ background: url(../images/ico_answers.gif) left 2px no-repeat;
+ padding-left: 24px;
+}
+
+.headTags {
+ float: left;
+ padding: 3px;
+ font-size: 18px;
+ font-weight: 800;
+ background: url(../images/ico_tags.gif) no-repeat;
+ padding-left: 24px;
+}
+
+.headUsers {
+ float: left;
+ height: 23px;
+ line-height: 23px;
+ margin: 5px 0 0 5px;
+ padding: 0px 6px 0px 15px;
+ font-size: 15px;
+ font-weight: 700;
+ border-bottom: 0px solid #777;
+ border-left: 0px solid #darkred;
+ background-color: #FFF;
+ background: url(../images/dot-list.gif) no-repeat left center;
+}
+
+.headMedals {
+ float: left;
+ height: 23px;
+ line-height: 23px;
+ margin: 5px 0 0 5px;
+ padding: 0px 6px 0px 15px;
+ font-size: 15px;
+ font-weight: 700;
+ border-bottom: 0px solid #777;
+ border-left: 0px solid #darkred;
+ background-color: #FFF;
+ background: url(../images/dot-list.gif) no-repeat left center;
+}
+
+.headLogin {
+ float: left;
+ padding: 3px;
+ font-size: 15px;
+ font-weight: 800;
+ background: url(../images/ico_login.gif) no-repeat;
+ padding-left: 24px;
+}
+
.headNormal {
- text-align:left;
- padding:3px;
- font-size:15px;
- margin-bottom:12px;
- font-weight:bold;
- border-bottom: 1px solid #777;
-}
-.headUser {text-align:left;padding:5px; font-size:20px;
- /*letter-spacing:1px;*/
- margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;}
+ text-align: left;
+ padding: 3px;
+ font-size: 15px;
+ margin-bottom: 12px;
+ font-weight: bold;
+ border-bottom: 1px solid #777;
+}
+
+.headUser {
+ text-align: left;
+ padding: 5px;
+ font-size: 20px; /*letter-spacing:1px;*/
+ margin-bottom: 12px;
+ font-weight: 800;
+ border-bottom: 1px solid #777;
+}
+
/*RSS订阅*/
-#feeds {margin:10px 0; }
-#feeds a {background:url(../images/feed-icon-small.png) no-repeat 0; padding-left:18px; font-weight:700; font-size:13px; }
+#feeds {
+ margin: 10px 0;
+}
+
+#feeds a {
+ background: url(../images/feed-icon-small.png) no-repeat 0;
+ padding-left: 18px;
+ font-weight: 700;
+ font-size: 13px;
+}
/*问题*/
-#question {margin-bottom:30px;}
-#question h1{font-size:15px;background:#CCC; padding:6px 8px;;}
-#question .body{background:#F7F7F7; padding:20px 10px;}
-.starter {padding:10px; background:#E0EAF1;}
-.vote {font-size:20px; color:#666; font-weight:800;}
-.questions-related{font-weight:700;word-wrap:break-word;}
-.questions-related p{line-height:20px; margin-bottom:10px;font-size:100%;}
-.question-status{
- margin-top:10px;
+#question {
+ margin-bottom: 30px;
+}
+
+#question h1 {
+ font-size: 15px;
+ background: #CCC;
+ padding: 6px 8px;;
+}
+
+#question .body {
+ background: #F7F7F7;
+ padding: 20px 10px;
+}
+
+.starter {
+ padding: 10px;
+ background: #E0EAF1;
+}
+
+.vote {
+ font-size: 20px;
+ color: #666;
+ font-weight: 800;
+}
+
+.questions-related {
+ font-weight: 700;
+ word-wrap: break-word;
+}
+
+.questions-related p {
+ line-height: 20px;
+ margin-bottom: 10px;
+ font-size: 100%;
+}
+
+.question-status {
+ margin-top: 10px;
padding: 20px;
- background-color:#F5F5F5;
- text-align:center;
+ background-color: #F5F5F5;
+ text-align: center;
+}
+
+.question-status h3 {
+ font-size: 125%;
}
-.question-status h3{font-size:125%;}
-.question-body{
- min-height:100px;
- font-size:13px;
- line-height:20px;
+
+.question-body {
+ min-height: 100px;
+ font-size: 13px;
+ line-height: 20px;
}
-.question-body IMG{
- max-width:600px;
+
+.question-body IMG {
+ max-width: 600px;
}
-.question-mark{
+
+.question-mark {
/*background-color:#fff5e0;
border-top: 1px solid #eeeeec;
border-right: 1px solid #babdb6;
border-bottom: 1px solid #babdb6;
border-left: 1px solid #eeeeec;*/
- text-align:left;
- padding:5px;
- overflow:hidden;
-}
-.question-edit{
- text-align:left;
- overflow:hidden;
-}
-.vote-buttons {float:left;text-align:center;}
-.vote-buttons IMG{cursor:pointer;}
-.vote-number{
- font-family:Arial;
- padding:0px 0 3px 0;
- font-size:140%;
- font-weight:bold;
- color:#777;
-}
-.question-img-upvote:hover{background:url(../images/vote-arrow-up-on.png)}
-.question-img-downvote:hover{background:url(../images/vote-arrow-down-on.png)}
-.question-img-favorite:hover{background:url(../images/vote-favorite-on.png)}
-.favorite-number{padding:0px;font-size:100%; font-family:Arial;font-weight:bold;color:#777;}
-.vote-notification
-{
- z-index: 1;
- cursor: pointer;
- display: none;
- position: absolute;
- padding: 15px;
+ text-align: left;
+ padding: 5px;
+ overflow: hidden;
+}
+
+.question-edit {
+ text-align: left;
+ overflow: hidden;
+}
+
+.vote-buttons {
+ float: left;
+ text-align: center;
+}
+
+.vote-buttons IMG {
+ cursor: pointer;
+}
+
+.vote-number {
+ font-family: Arial;
+ padding: 0px 0 3px 0;
+ font-size: 140%;
+ font-weight: bold;
+ color: #777;
+}
+
+.question-img-upvote:hover {
+ background: url(../images/vote-arrow-up-on.png)
+}
+
+.question-img-downvote:hover {
+ background: url(../images/vote-arrow-down-on.png)
+}
+
+.question-img-favorite:hover {
+ background: url(../images/vote-favorite-on.png)
+}
+
+.favorite-number {
+ padding: 0px;
+ font-size: 100%;
+ font-family: Arial;
+ font-weight: bold;
+ color: #777;
+}
+
+.vote-notification {
+ z-index: 1;
+ cursor: pointer;
+ display: none;
+ position: absolute;
+ padding: 15px;
color: White;
- background-color: darkred;
- text-align: center;
+ background-color: darkred;
+ text-align: center;
}
-.vote-notification a
-{
+
+.vote-notification a {
color: White;
- text-decoration:underline;
+ text-decoration: underline;
}
-.offensive-flag a{
- color:#777;
- padding:3px;
- cursor:pointer;
+
+.offensive-flag a {
+ color: #777;
+ padding: 3px;
+ cursor: pointer;
}
-.offensive-flag a:hover{
- background-color:#777;
- text-decoration:none;
- color:#fff;
+.offensive-flag a:hover {
+ background-color: #777;
+ text-decoration: none;
+ color: #fff;
}
-.linksopt a{
- color:#777;
- padding:3px;
- cursor:pointer;
+.linksopt a {
+ color: #777;
+ padding: 3px;
+ cursor: pointer;
}
-.linksopt a:hover{
- background-color:#777;
- text-decoration:none;
- color:#fff;
+.linksopt a:hover {
+ background-color: #777;
+ text-decoration: none;
+ color: #fff;
}
-.action-link a{
- color:#777;
- padding:3px;
- cursor:pointer;
+.action-link a {
+ color: #777;
+ padding: 3px;
+ cursor: pointer;
}
-.action-link: a hover{
- background-color:#777;
- text-decoration:none;
- color:#fff;
+.action-link: a hover {
+ background-color: #777;
+ text-decoration: none;
+ color: #fff;
}
-.action-link-separator{
- color:#ccc;
+
+.action-link-separator {
+ color: #ccc;
}
-.wiki-category{
- margin-left:5px;
- color:#999;
- font-size:90%;
+
+.wiki-category {
+ margin-left: 5px;
+ color: #999;
+ font-size: 90%;
}
div.comments {
- line-height:150%;
- padding:10px 0;
+ line-height: 150%;
+ padding: 10px 0;
}
-div.post-comments{
- clear:both;
+div.post-comments {
+ clear: both;
background: url(../images/gray-up-arrow-h18px.png) no-repeat;
- width:100%;
+ width: 100%;
padding-left: 12px;
- margin:3px 0 10px 0;
+ margin: 3px 0 10px 0;
}
form.post-comments textarea {
- height:6em;
- margin-bottom:4px;
+ height: 6em;
+ margin-bottom: 4px;
}
form.post-comments input {
- margin-left:10px;
- margin-top:1px;
- vertical-align:top;
- width:100px;
+ margin-left: 10px;
+ margin-top: 1px;
+ vertical-align: top;
+ width: 100px;
}
+
span.text-counter {
- margin-right:20px;
- font-size:11px;
+ margin-right: 20px;
+ font-size: 11px;
}
span.form-error {
- color:#990000;
- font-weight:normal;
- margin-left:5px;
+ color: #990000;
+ font-weight: normal;
+ margin-left: 5px;
}
+
p.form-item {
- margin:0px;
+ margin: 0px;
}
div.comments-container, div.comments-container-accepted, div.comments-container-owner, div.comments-container-deleted {
- padding:0;
+ padding: 0;
}
.post-comments a {
- color:#888888;
- padding:0 3px 2px;
+ color: #888888;
+ padding: 0 3px 2px;
}
a.comments-link, a.comments-link-accepted, a.comments-link-owner, a.comments-link-deleted {
- color:black;
- font-size:11px;
+ color: black;
+ font-size: 11px;
background: #eeeeee;
- padding:3px;
- cursor:pointer;
+ padding: 3px;
+ cursor: pointer;
}
.post-comments a:hover {
- background-color:#777777;
- color:white;
- text-decoration:none;
+ background-color: #777777;
+ color: white;
+ text-decoration: none;
}
a.comment-user, a.comment-user:hover {
- background-color:inherit;
- color:blue;
- padding:0;
+ background-color: inherit;
+ color: blue;
+ padding: 0;
}
a.comment-user:hover {
- text-decoration:underline;
+ text-decoration: underline;
}
+
/*回答*/
-#answers {}
-.answer{
- padding-top:10px;
+#answers {
+}
+
+.answer {
+ padding-top: 10px;
width: 100%;
- border-bottom:1px solid #ccccce;
+ border-bottom: 1px solid #ccccce;
}
-.answer-body{
- min-height:80px;
- font-size:13px;
- line-height:20px;
+
+.answer-body {
+ min-height: 80px;
+ font-size: 13px;
+ line-height: 20px;
}
-.answer-body IMG{
- max-width:600px;
+.answer-body IMG {
+ max-width: 600px;
}
-.accepted-answer{
- background-color:#EBFFE6;
- border-bottom-color:#9BD59B;
+.accepted-answer {
+ background-color: #EBFFE6;
+ border-bottom-color: #9BD59B;
}
-.accepted-answer .comments-link{
- background-color:#CCFFBF;
+.accepted-answer .comments-link {
+ background-color: #CCFFBF;
}
-.accepted-answer .comments-container{
- background-color:#CCFFBF;
+.accepted-answer .comments-container {
+ background-color: #CCFFBF;
}
-.answered
-{
- background: #CCC;
- color: #999;
+.answered {
+ background: #CCC;
+ color: #999;
}
-.answered-accepted
-{
- background: #CCC;
- color: #763333;
+.answered-accepted {
+ background: #CCC;
+ color: #763333;
}
-.unanswered
-{
- background: #777;
- color: white;
+.unanswered {
+ background: #777;
+ color: white;
}
-.answered-by-owner
-{
- background: #E9E9FF;
+.answered-by-owner {
+ background: #E9E9FF;
}
-.answered-by-owner .comments-link
-{
- background-color:#E6ECFF;
+.answered-by-owner .comments-link {
+ background-color: #E6ECFF;
}
-.answered-by-owner .comments-container
-{
- background-color:#E6ECFF;
+.answered-by-owner .comments-container {
+ background-color: #E6ECFF;
}
-.answered-accepted strong
-{
- color: #E1E818;
+.answered-accepted strong {
+ color: #E1E818;
}
-.answer-img-accept:hover{background:url(../images/vote-accepted-on.png)}
+.answer-img-accept:hover {
+ background: url(../images/vote-accepted-on.png)
+}
-.deleted{
- background:#F4E7E7 none repeat scroll 0 0;
+.deleted {
+ background: #F4E7E7 none repeat scroll 0 0;
}
/*标签列表*/
@@ -658,813 +1342,1118 @@ a.comment-user:hover {
.tagsbox {}
.tagsbox a {color:#000;line-height:30px;margin-right:10px;font-size:100%;background-color:#F9F7ED;padding:3px;border:1px solid #aaaaaa;}
.tagsbox a:hover {text-decoration:none;background-color:#F9F7ED;color:#B02B2C;} */
-.tagsList {margin:0; list-style-type:none;padding:0px;min-height:360px;}
-.tagsList li {width:235px; float:left;}
-.badge-list{margin:0; list-style-type:none;}
+.tagsList {
+ margin: 0;
+ list-style-type: none;
+ padding: 0px;
+ min-height: 360px;
+}
+
+.tagsList li {
+ width: 235px;
+ float: left;
+}
+
+.badge-list {
+ margin: 0;
+ list-style-type: none;
+}
+
/*登录*/
-.list-item{margin-left:15px;}
-.list-item LI{list-style-type:disc; font-size:13px; line-height:20px; margin-bottom:10px;}
+.list-item {
+ margin-left: 15px;
+}
+
+.list-item LI {
+ list-style-type: disc;
+ font-size: 13px;
+ line-height: 20px;
+ margin-bottom: 10px;
+}
+
/* openid styles */
-.form-row{line-height:25px;}
+.form-row {
+ line-height: 25px;
+}
+
table.form-as-table {
- margin-top:5px;
+ margin-top: 5px;
}
+
table.form-as-table ul {
- list-style-type:none;
+ list-style-type: none;
display: inline;
}
+
table.form-as-table li {
display: inline;
}
+
table.form-as-table td {
- text-align:right;
+ text-align: right;
}
+
table.form-as-table th {
- text-align:left;
- font-weight:normal;
+ text-align: left;
+ font-weight: normal;
}
+
/*.form-row li label {
display: inline
}*/
-.submit-row{
- line-height:30px;
- padding-top:10px;
+.submit-row {
+ line-height: 30px;
+ padding-top: 10px;
display: block;
clear: both;
}
-.errors{line-height:20px;color:red;}
-.error{
- color:darkred;
- margin:0;
+
+.errors {
+ line-height: 20px;
+ color: red;
+}
+
+.error {
+ color: darkred;
+ margin: 0;
font-size: 10px;
}
-.error-list li{padding:5px;}
-.fieldset{
+
+.error-list li {
+ padding: 5px;
+}
+
+.fieldset {
/* border:solid 1px #777;*/
- border: none;
- margin-top:10px;
- padding:10px;
+ border: none;
+ margin-top: 10px;
+ padding: 10px;
}
-.openid-input{background:url(../images/openid.gif) no-repeat;padding-left:15px;cursor:pointer;}
-.openid-login-input{
- background-position:center left;
- background:url(../images/openid.gif) no-repeat 0% 50%;
- padding:5px 5px 5px 15px;
- cursor:pointer;
- font-family:Trebuchet MS;
- font-weight:300;
- font-size:150%;
- width:500px;
+
+.openid-input {
+ background: url(../images/openid.gif) no-repeat;
+ padding-left: 15px;
+ cursor: pointer;
+}
+
+.openid-login-input {
+ background-position: center left;
+ background: url(../images/openid.gif) no-repeat 0% 50%;
+ padding: 5px 5px 5px 15px;
+ cursor: pointer;
+ font-family: Trebuchet MS;
+ font-weight: 300;
+ font-size: 150%;
+ width: 500px;
}
-.openid-login-submit{
- height:40px;
- width:80px;
- line-height:40px;
- cursor:pointer;
- border:1px solid #777;
- font-weight:bold;
- font-size:120%;
+.openid-login-submit {
+ height: 40px;
+ width: 80px;
+ line-height: 40px;
+ cursor: pointer;
+ border: 1px solid #777;
+ font-weight: bold;
+ font-size: 120%;
}
-.openid-samples{
+.openid-samples {
}
-.openid-samples .list, .list li{
- font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
- list-style:none !important;
- margin-left:-30px !important;
- line-height:20px !important;
+.openid-samples .list, .list li {
+ font-family: Trebuchet MS, "segoe ui", Helvetica, "Microsoft YaHei", 宋 体, Tahoma, Verdana, MingLiu, PMingLiu, Arial, sans-serif;
+ list-style: none !important;
+ margin-left: -30px !important;
+ line-height: 20px !important;
}
/*表单相关*/
span.form-error {
- color:#990000;
- font-size:90%;
- font-weight:normal;
- margin-left:5px;
+ color: #990000;
+ font-size: 90%;
+ font-weight: normal;
+ margin-left: 5px;
}
-.title-desc{
- color:#666666;
- font-size:90%;
+
+.title-desc {
+ color: #666666;
+ font-size: 90%;
}
/*adjustment for editor preview*/
-#editor{
- font-size:100%;
- min-height:200px;
+#editor {
+ font-size: 100%;
+ min-height: 200px;
line-height: 18px;
- width:100%;
+ width: 100%;
}
-.wmd-preview{
- margin-top:10px;
- padding:6px;
- width:100%;
- background-color:#F5F5F5;
- min-height:20px;
+.wmd-preview {
+ margin-top: 10px;
+ padding: 6px;
+ width: 100%;
+ background-color: #F5F5F5;
+ min-height: 20px;
}
-.wmd-preview pre{
- background-color:#E7F1F8;
+
+.wmd-preview pre {
+ background-color: #E7F1F8;
}
-.wmd-preview blockquote
-{
- background-color: #eee;
+.wmd-preview blockquote {
+ background-color: #eee;
}
-.wmd-preview IMG{
- max-width:600px;
+.wmd-preview IMG {
+ max-width: 600px;
}
-.preview-toggle{
- font-weight:600;
- width:100%;
- color:#aaa;
- /*letter-spacing:1px;*/
- text-align:left;
+
+.preview-toggle {
+ font-weight: 600;
+ width: 100%;
+ color: #aaa; /*letter-spacing:1px;*/
+ text-align: left;
}
-.preview-toggle span:hover{
- cursor:pointer;
+.preview-toggle span:hover {
+ cursor: pointer;
}
-.edit-content-html{
- border-top:1px dotted #D8D2A9;
- border-bottom:1px dotted #D8D2A9;
- margin:5px 0 5px 0;
+.edit-content-html {
+ border-top: 1px dotted #D8D2A9;
+ border-bottom: 1px dotted #D8D2A9;
+ margin: 5px 0 5px 0;
}
/*修订记录*/
-#revisions{
- width:950px;
+#revisions {
+ width: 950px;
}
-.revision{
- margin:10px 0 10px 0;
- width:100%;
- font-size:13px;
+.revision {
+ margin: 10px 0 10px 0;
+ width: 100%;
+ font-size: 13px;
}
-.revision .header{
- background-color:#eee;
- padding:5px;
- cursor:pointer;
+.revision .header {
+ background-color: #eee;
+ padding: 5px;
+ cursor: pointer;
}
-.revision .author{
- background-color:#E9E9FF;
+.revision .author {
+ background-color: #E9E9FF;
}
-.revision .summary{
+.revision .summary {
padding: 5px 0 10px 0;
}
-.revision .summary span{
- background-color:yellow;
- padding-left:3px;
- padding-right:3px;
- display:inline;
+.revision .summary span {
+ background-color: yellow;
+ padding-left: 3px;
+ padding-right: 3px;
+ display: inline;
}
-.revision h1{
- font-size:130%;
- font-weight:600;
- padding:15px 0 15px 0;
+
+.revision h1 {
+ font-size: 130%;
+ font-weight: 600;
+ padding: 15px 0 15px 0;
}
-.revision-mark{
- width:200px;
- text-align:left;
- display:inline-block;
- font-size:90%;
- overflow:hidden;
+.revision-mark {
+ width: 200px;
+ text-align: left;
+ display: inline-block;
+ font-size: 90%;
+ overflow: hidden;
}
-.revision-number{
- font-size:300%;
- font-weight:bold;
- font-family:sans-serif;
+.revision-number {
+ font-size: 300%;
+ font-weight: bold;
+ font-family: sans-serif;
}
-.revision .body{
- padding-left:10px;
- margin-bottom:50px;
+.revision .body {
+ padding-left: 10px;
+ margin-bottom: 50px;
}
-.revision .answerbody{
- padding:10px 0 5px 10px;
+
+.revision .answerbody {
+ padding: 10px 0 5px 10px;
}
/* Revision pages */
-del { color: #FF5F5F; }
-del .post-tag{
-color: #FF5F5F;
+del {
+ color: #FF5F5F;
+}
+
+del .post-tag {
+ color: #FF5F5F;
}
-ins { background-color: #97ff97;}
-ins .post-tag{
-background-color: #97ff97;
+
+ins {
+ background-color: #97ff97;
+}
+
+ins .post-tag {
+ background-color: #97ff97;
}
/*用户资料页面*/
-.count {font-family:Arial;font-size:200%;font-weight:700;color:#777}
-.scoreNumber{font-family:Arial;font-size:35px;font-weight:800;color:#777;line-height:40px;
- /*letter-spacing:0px*/
- }
-.user-details{font-size:13px;}
-.user-about{background-color:#EEEEEE;height:200px;line-height:20px; overflow:auto;padding:10px;width:90%;}
-.user-edit-link {background:url(../images/edit.png) no-repeat; padding-left:20px;}
+.count {
+ font-family: Arial;
+ font-size: 200%;
+ font-weight: 700;
+ color: #777
+}
+
+.scoreNumber {
+ font-family: Arial;
+ font-size: 35px;
+ font-weight: 800;
+ color: #777;
+ line-height: 40px; /*letter-spacing:0px*/
+}
+
+.user-details {
+ font-size: 13px;
+}
+
+.user-about {
+ background-color: #EEEEEE;
+ height: 200px;
+ line-height: 20px;
+ overflow: auto;
+ padding: 10px;
+ width: 90%;
+}
+
+.user-edit-link {
+ background: url(../images/edit.png) no-repeat;
+ padding-left: 20px;
+}
+
.favorites-count-off {
- color:#919191;
- float:left;
- padding:3px;
- margin:10px 0 0 0 ;
- text-align:center;
+ color: #919191;
+ float: left;
+ padding: 3px;
+ margin: 10px 0 0 0;
+ text-align: center;
}
.favorites-count {
- color:#D4A849;
- float:left;
- padding:3px;
- margin:10px 0 0 0 ;
- text-align:center;
+ color: #D4A849;
+ float: left;
+ padding: 3px;
+ margin: 10px 0 0 0;
+ text-align: center;
}
-.favorites-empty{
- width: 32px; height: 45px; float: left;
+
+.favorites-empty {
+ width: 32px;
+ height: 45px;
+ float: left;
}
+
.question-summary {
- border-bottom:1px dotted #999999;
- float:left;
- overflow:hidden;
- padding:11px 0;
- width:670px;
+ border-bottom: 1px dotted #999999;
+ float: left;
+ overflow: hidden;
+ padding: 11px 0;
+ width: 670px;
}
-.user-info-table{
-width:950;margin-bottom:10px;
+.user-info-table {
+ width: 950;
+ margin-bottom: 10px;
}
.user-stats-table .question-summary {
- width:800px;
+ width: 800px;
}
.narrow .stats {
- background:transparent none repeat scroll 0 0;
- float:left;
- height:48px;
- margin:0 0 0 7px;
- padding:0;
- width:auto;
- font-family:Arial;
+ background: transparent none repeat scroll 0 0;
+ float: left;
+ height: 48px;
+ margin: 0 0 0 7px;
+ padding: 0;
+ width: auto;
+ font-family: Arial;
}
.stats div {
- font-size:11px;
- text-align:center;
+ font-size: 11px;
+ text-align: center;
}
.narrow .votes {
- background:#EEEEEE none repeat scroll 0 0;
- float:left;
- height:42px;
- margin:0 3px 0 0;
- padding:5px;
- width:46px;
- text-align:center;
+ background: #EEEEEE none repeat scroll 0 0;
+ float: left;
+ height: 42px;
+ margin: 0 3px 0 0;
+ padding: 5px;
+ width: 46px;
+ text-align: center;
-moz-border-radius: 5px;
-khtml-border-radius: 5px;
-webkit-border-radius: 5px;
}
.narrow .summary {
- width:600px;
- display:inline-block;
+ width: 600px;
+ display: inline-block;
}
.narrow .summary h3 {
- padding:0px;
- margin:0px;
+ padding: 0px;
+ margin: 0px;
}
.narrow .views {
- height:42px;
- float:left;
- margin:0 7px 0 0;
- /*padding:5px 0 5px 4px;*/
- padding: 5px;
- width:46px;
- text-align:center;
+ height: 42px;
+ float: left;
+ margin: 0 7px 0 0; /*padding:5px 0 5px 4px;*/
+ padding: 5px;
+ width: 46px;
+ text-align: center;
-moz-border-radius: 5px;
-khtml-border-radius: 5px;
-webkit-border-radius: 5px;
- color:#777;
+ color: #777;
}
.narrow .status {
- float:left;
- height:42px;
- margin:0 3px 0 0;
- padding:5px;
- width:46px;
- text-align:center;
+ float: left;
+ height: 42px;
+ margin: 0 3px 0 0;
+ padding: 5px;
+ width: 46px;
+ text-align: center;
-moz-border-radius: 5px;
-khtml-border-radius: 5px;
-webkit-border-radius: 5px;
}
.narrow .vote-count-post {
- font-weight:800;
- display:block;
- margin:0;
- font-size: 190%; color:#555; line-height:20px;
-}
-.narrow .answer-count-post{
- font-weight:800;
- display:block;
- margin:0;
- font-size: 190%;
-}
-.narrow .views-count-post{
- font-weight:800;
- display:block;
- margin:0;
+ font-weight: 800;
+ display: block;
+ margin: 0;
+ font-size: 190%;
+ color: #555;
+ line-height: 20px;
+}
+
+.narrow .answer-count-post {
+ font-weight: 800;
+ display: block;
+ margin: 0;
+ font-size: 190%;
+}
+
+.narrow .views-count-post {
+ font-weight: 800;
+ display: block;
+ margin: 0;
font-size: 190%;
}
+
div.started {
- color:#999999;
- float:right;
- line-height:18px;
-
+ color: #999999;
+ float: right;
+ line-height: 18px;
+
}
.narrow div.started {
- line-height:inherit;
- padding-top:4px;
- white-space:nowrap;
- width:auto;
+ line-height: inherit;
+ padding-top: 4px;
+ white-space: nowrap;
+ width: auto;
}
.relativetime {
- font-weight:bold;
- text-decoration:none;
+ font-weight: bold;
+ text-decoration: none;
}
div.started a {
- font-weight:bold;
+ font-weight: bold;
}
div.started .reputation-score {
- margin-left:1px;
+ margin-left: 1px;
}
-.narrow .tags{float:left;}
+.narrow .tags {
+ float: left;
+}
.answer-summary {
- display:block;
- clear:both;
- padding:3px;
+ display: block;
+ clear: both;
+ padding: 3px;
}
.answer-votes {
- background-color:#EEEEEE;
- color:#555555;
- float:left;
- font-family:Arial;
- font-size:110%;
- font-weight:bold;
- height:15px;
- padding:4px 4px 5px;
- text-align:center;
- text-decoration:none;
- width:20px;
- margin-right:10px;
+ background-color: #EEEEEE;
+ color: #555555;
+ float: left;
+ font-family: Arial;
+ font-size: 110%;
+ font-weight: bold;
+ height: 15px;
+ padding: 4px 4px 5px;
+ text-align: center;
+ text-decoration: none;
+ width: 20px;
+ margin-right: 10px;
+}
+
+.vote-count {
+ font-family: Arial;
+ font-size: 160%;
+ font-weight: 700;
+ color: #777;
}
-.vote-count{font-family:Arial; font-size:160%; font-weight:700; color:#777;}
-.user-action{
+
+.user-action {
}
-.user-action-1{
- font-weight:bold;
- color:#333;
+
+.user-action-1 {
+ font-weight: bold;
+ color: #333;
}
-.user-action-2{
- font-weight:bold;
- color:#CCC;
+
+.user-action-2 {
+ font-weight: bold;
+ color: #CCC;
}
-.user-action-3{
- color:#333;
+
+.user-action-3 {
+ color: #333;
}
-.user-action-4{
- color:#333;
+
+.user-action-4 {
+ color: #333;
}
-.user-action-5{
- color:darkred;
+
+.user-action-5 {
+ color: darkred;
}
-.user-action-6{
- color:darkred;
+
+.user-action-6 {
+ color: darkred;
}
-.user-action-7{
- color:#333;
+
+.user-action-7 {
+ color: #333;
}
-.user-action-8{
- padding:3px;
- font-weight:bold;
- background-color:#CCC;
- color:#763333;
+
+.user-action-8 {
+ padding: 3px;
+ font-weight: bold;
+ background-color: #CCC;
+ color: #763333;
}
-.revision-summary{
- background-color:#FFFE9B;
- padding:2px;
+.revision-summary {
+ background-color: #FFFE9B;
+ padding: 2px;
}
-.question-title-link a{
- font-weight:bold;
- color:#0077CC;
+
+.question-title-link a {
+ font-weight: bold;
+ color: #0077CC;
}
-.answer-title-link a{
- color:#333;
+
+.answer-title-link a {
+ color: #333;
}
.post-type-1 a {
- font-weight:bold;
-
+ font-weight: bold;
+
}
+
.post-type-3 a {
- font-weight:bold;
-
+ font-weight: bold;
+
}
+
.post-type-5 a {
- font-weight:bold;
- }
-.post-type-2 a{
- color:#333;
+ font-weight: bold;
}
-.post-type-4 a{
- color:#333;
+
+.post-type-2 a {
+ color: #333;
}
-.post-type-6 a{
- color:#333;
+
+.post-type-4 a {
+ color: #333;
}
-.post-type-8 a{
- color:#333;
+
+.post-type-6 a {
+ color: #333;
}
+.post-type-8 a {
+ color: #333;
+}
/*读书频道*/
-.bookInfo {float:left; width:940px;padding:5px;}
-.bookCover {float:left; width:200px;}
-.bookCover img{border:1px solid #ccc;max-width:200px;}
-.bookSummary {float:left; font-size:13px;}
-.blogRss {float:right;margin:0 10px 0 0;width:460px;height:240px;background-color:#EEE; padding:5px;}
-.bookQuestions {margin-bottom:10px;}
-.bookFeed {float:right;}
-.bookAsk{
- /*letter-spacing:1px; */
- float:right;margin:-30px 10px 0 0; padding:3px 5px 3px 5px;}
-.bookAsk a {font-size:15px; color:#FFF; font-weight:bold; text-decoration:none;background-color:#EC7000;padding:3px 6px 3px 6px; }
-.bookAsk a:hover {text-decoration:underline;}
+.bookInfo {
+ float: left;
+ width: 940px;
+ padding: 5px;
+}
+
+.bookCover {
+ float: left;
+ width: 200px;
+}
+
+.bookCover img {
+ border: 1px solid #ccc;
+ max-width: 200px;
+}
+
+.bookSummary {
+ float: left;
+ font-size: 13px;
+}
+
+.blogRss {
+ float: right;
+ margin: 0 10px 0 0;
+ width: 460px;
+ height: 240px;
+ background-color: #EEE;
+ padding: 5px;
+}
+
+.bookQuestions {
+ margin-bottom: 10px;
+}
+
+.bookFeed {
+ float: right;
+}
+
+.bookAsk {
+/*letter-spacing:1px; */
+ float: right;
+ margin: -30px 10px 0 0;
+ padding: 3px 5px 3px 5px;
+}
+
+.bookAsk a {
+ font-size: 15px;
+ color: #FFF;
+ font-weight: bold;
+ text-decoration: none;
+ background-color: #EC7000;
+ padding: 3px 6px 3px 6px;
+}
+.bookAsk a:hover {
+ text-decoration: underline;
+}
/*其他全局样式*/
-.hilite { background-color: #ff0; }
-.hilite1 { background-color: #ff0; }
-.hilite2 { background-color: #f0f; }
-.hilite3 { background-color: #0ff; }
-.userStatus {margin-left:12px; color:#FFF; float:right;}
-.userStatus a {color:#FFF;}
-.gold, .badge1 {color:#FFCC00;}
-.silver, .badge2 {color:#CCCCCC;}
-.bronze, .badge3 {color:#CC9933;}
-.score {font-weight:800; color:#333;}
-.footerLinks {color:#EEE; font-size:13px;
- /* letter-spacing:1px;*/
- }
-.footerLinks a {color:#FFF; font-size:13px;}
-.subSearch {margin-bottom:12px; padding:4px;}
-a.comment {background:#EEE; color:#993300; padding:4px;}
-a.permLink {padding:2px;}
-a.offensive {color:#999;}
-ul.bulleta li {background:url(../images/bullet_green.gif) no-repeat 0px 2px; padding-left:16px; margin-bottom:4px;}
-.user {padding:5px; line-height:140%; width:170px;}
-.user ul {margin:0; list-style-type:none;}
-.user .thumb{clear:both;float:left; margin-right:4px; display:inline;}
-.yellowbg{background:yellow;}
-
-.message{
- padding:5px;
- margin:10px 0 10px 0;
- background-color:#eee;
+.hilite {
+ background-color: #ff0;
+}
+
+.hilite1 {
+ background-color: #ff0;
+}
+
+.hilite2 {
+ background-color: #f0f;
+}
+
+.hilite3 {
+ background-color: #0ff;
+}
+
+.userStatus {
+ margin-left: 12px;
+ color: #FFF;
+ float: right;
+}
+
+.userStatus a {
+ color: #FFF;
+}
+
+.gold, .badge1 {
+ color: #FFCC00;
+}
+
+.silver, .badge2 {
+ color: #CCCCCC;
+}
+
+.bronze, .badge3 {
+ color: #CC9933;
+}
+
+.score {
+ font-weight: 800;
+ color: #333;
+}
+
+.footerLinks {
+ color: #EEE;
+ font-size: 13px; /* letter-spacing:1px;*/
+}
+
+.footerLinks a {
+ color: #FFF;
+ font-size: 13px;
+}
+
+.subSearch {
+ margin-bottom: 12px;
+ padding: 4px;
+}
+
+a.comment {
+ background: #EEE;
+ color: #993300;
+ padding: 4px;
+}
+
+a.permLink {
+ padding: 2px;
+}
+
+a.offensive {
+ color: #999;
+}
+
+ul.bulleta li {
+ background: url(../images/bullet_green.gif) no-repeat 0px 2px;
+ padding-left: 16px;
+ margin-bottom: 4px;
+}
+
+.user {
+ padding: 5px;
+ line-height: 140%;
+ width: 170px;
+}
+
+.user ul {
+ margin: 0;
+ list-style-type: none;
+}
+
+.user .thumb {
+ clear: both;
+ float: left;
+ margin-right: 4px;
+ display: inline;
+}
+
+.yellowbg {
+ background: yellow;
+}
+
+.message {
+ padding: 5px;
+ margin: 10px 0 10px 0;
+ background-color: #eee;
border: 1px solid #aaaaaa;
}
+
.message h1 {
- padding-top:0px;
- font-size:15px;
+ padding-top: 0px;
+ font-size: 15px;
}
+
.message p {
- margin-bottom:0px;
+ margin-bottom: 0px;
}
p.space-above {
- margin-top:10px;
-}
-
-.warning{color:red;}
-.darkred{color:darkred;}
-.submit{
- cursor:pointer;
- /*letter-spacing:1px;*/
- background-color:#D4D0C8;
- height:40px;
- border:1px solid #777777;
-/* width:100px; */
- font-weight:bold;
- padding-bottom:4px;
- font-size:120%;}
-.submit:hover{text-decoration:underline;}
-.ask-body{padding-right:10px;}
-.thousand{color:orange;}
-
-.notify
-{
- position: fixed;
- top: 0px;
- left: 0px;
- width: 100%;
- z-index: 100;
- padding: 0;
- text-align: center;
- font-weight: Bold;
- color: #444;
- background-color: #F4A83D;
+ margin-top: 10px;
+}
+
+.warning {
+ color: red;
+}
+
+.darkred {
+ color: darkred;
+}
+
+.submit {
+ cursor: pointer; /*letter-spacing:1px;*/
+ background-color: #D4D0C8;
+ height: 40px;
+ border: 1px solid #777777; /* width:100px; */
+ font-weight: bold;
+ padding-bottom: 4px;
+ font-size: 120%;
+}
+
+.submit:hover {
+ text-decoration: underline;
+}
+
+.ask-body {
+ padding-right: 10px;
+}
+
+.thousand {
+ color: orange;
+}
+
+.notify {
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ z-index: 100;
+ padding: 0;
+ text-align: center;
+ font-weight: Bold;
+ color: #444;
+ background-color: #F4A83D;
}
.notify p {
- margin-top:5px;
- margin-bottom:5px;
- font-size:16px;
-}
-
-#close-notify
-{
- position:absolute;
- right:5px;
- top:5px;
- padding:0 3px 0 3px;
- color: #735005;
- text-decoration: none;
- font-size:14px;
- line-height:18px;
- background-color: #FAD163;
- border: 2px #735005 solid;
- cursor:pointer;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ font-size: 16px;
+}
+
+#close-notify {
+ position: absolute;
+ right: 5px;
+ top: 5px;
+ padding: 0 3px 0 3px;
+ color: #735005;
+ text-decoration: none;
+ font-size: 14px;
+ line-height: 18px;
+ background-color: #FAD163;
+ border: 2px #735005 solid;
+ cursor: pointer;
}
+
#close-notify:hover {
- text-decoration:none;
+ text-decoration: none;
}
.big {
- font-size:15px;
+ font-size: 15px;
}
+
.bigger {
- font-size:14px;
+ font-size: 14px;
}
+
.strong {
- font-weight:bold;
+ font-weight: bold;
}
-.orange
-{
- color:#d64000;
- font-weight:bold;
+
+.orange {
+ color: #d64000;
+ font-weight: bold;
}
+
.grey {
- color:#808080;
+ color: #808080;
}
+
.about div {
- padding:10px 5px 10px 5px;
- border-top:1px dashed #aaaaaa;
+ padding: 10px 5px 10px 5px;
+ border-top: 1px dashed #aaaaaa;
}
+
.about div.first {
- padding-top:0;
- border-top:none;
+ padding-top: 0;
+ border-top: none;
}
+
.about p {
- margin-bottom:10px;
+ margin-bottom: 10px;
+}
+
+.about a {
+ color: #d64000;
+ text-decoration: underline;
}
-.about a {color:#d64000;text-decoration:underline;}
-.about h3{
- line-height:30px;
- font-size:15px;
- font-weight:700;
+
+.about h3 {
+ line-height: 30px;
+ font-size: 15px;
+ font-weight: 700;
padding-top: 0px;
}
+
.highlight {
- background-color:#FFF8C6;
+ background-color: #FFF8C6;
}
+
.nomargin {
- margin:0;
+ margin: 0;
}
+
.margin-bottom {
margin-bottom: 10px;
}
+
.margin-top {
margin-top: 10px;
}
+
.inline-block {
- display:inline-block;
+ display: inline-block;
}
+
.action-status {
- margin:0;
- border:none;
- text-align:center;
- line-height:10px;
- font-size:12px;
- padding:0;
+ margin: 0;
+ border: none;
+ text-align: center;
+ line-height: 10px;
+ font-size: 12px;
+ padding: 0;
}
+
.action-status span {
- padding:3px 5px 3px 5px;
- background-color:#fff380;/* nice yellow */
- font-weight:normal;
+ padding: 3px 5px 3px 5px;
+ background-color: #fff380; /* nice yellow */
+ font-weight: normal;
-moz-border-radius: 5px;
-khtml-border-radius: 5px;
-webkit-border-radius: 5px;
}
+
.tight {
- margin:0;
- padding:0;
+ margin: 0;
+ padding: 0;
}
.list-table td {
- vertical-align:top;
+ vertical-align: top;
}
p.comment {
border-top: 1px dotted #ccccce;
- margin:0;
- font-size:11px;
+ margin: 0;
+ font-size: 11px;
color: #444444;
- padding:5px 0 5px 0;
+ padding: 5px 0 5px 0;
}
.delete-icon {
- vertical-align:middle;
- padding-left:3px;
+ vertical-align: middle;
+ padding-left: 3px;
}
+
/* these need to go */
table.form-as-table .errorlist {
display: block;
- margin:0;
- padding:0 0 0 5px;
- text-align:left;
- font-size:10px;
- color:darkred;
+ margin: 0;
+ padding: 0 0 0 5px;
+ text-align: left;
+ font-size: 10px;
+ color: darkred;
}
+
table.form-as-table input {
display: inline;
margin-left: 4px;
}
+
table.form-as-table th {
- vertical-align:bottom;
- padding-bottom:4px;
+ vertical-align: bottom;
+ padding-bottom: 4px;
}
+
.form-row-vertical {
margin-top: 8px;
display: block;
}
+
.form-row-vertical label {
- margin-bottom:3px;
- display:block;
+ margin-bottom: 3px;
+ display: block;
}
+
/* above stuff needs to go */
.text-align-right {
text-align: center;
}
+
ul.form-horizontal-rows {
- list-style:none;
- margin:0;
+ list-style: none;
+ margin: 0;
}
+
ul.form-horizontal-rows li {
- position:relative;
- height:40px;
+ position: relative;
+ height: 40px;
}
+
ul.form-horizontal-rows label {
- display:inline-block;
+ display: inline-block;
}
+
ul.form-horizontal-rows ul.errorlist {
- list-style:none;
- color:darkred;
- font-size:10px;
- line-height:10px;
- position:absolute;
- top:2px;
- left:180px;
- text-align:left;
- margin:0;
+ list-style: none;
+ color: darkred;
+ font-size: 10px;
+ line-height: 10px;
+ position: absolute;
+ top: 2px;
+ left: 180px;
+ text-align: left;
+ margin: 0;
}
+
ul.form-horizontal-rows ul.errorlist li {
- height:10px;
+ height: 10px;
}
+
ul.form-horizontal-rows label {
- position:absolute;
- left:0px;
- bottom:6px;
- margin:0px;
+ position: absolute;
+ left: 0px;
+ bottom: 6px;
+ margin: 0px;
line-height: 12px;
font-size: 12px;
}
+
ul.form-horizontal-rows li input {
- position:absolute;
- bottom:0px;
- left:180px;
- margin:0px;
+ position: absolute;
+ bottom: 0px;
+ left: 180px;
+ margin: 0px;
}
+
#emailpw-form li input {
- left:170px;
+ left: 170px;
}
+
#emailpw-form ul.errorlist {
- left:170px;
+ left: 170px;
}
+
#changepw-form li input {
- left:150px;
+ left: 150px;
}
+
#changepw-form ul.errorlist {
- left:150px;
+ left: 150px;
}
+
.narrow .summary {
float: left;
}
+
.narrow .summary .question-title {
font-weight: bold;
font-size: 120%;
}
+
.user-profile-tool-links {
- padding-bottom:10px;
+ padding-bottom: 10px;
font-weight: bold;
}
+
.post-controls {
- float:left;
- font-size:11px;
- line-height:12px;
- min-width:200px;
- margin-bottom:5px;
+ float: left;
+ font-size: 11px;
+ line-height: 12px;
+ min-width: 200px;
+ margin-bottom: 5px;
}
+
#question-controls .tags {
- margin:0 0 3px 0;
+ margin: 0 0 3px 0;
}
+
.post-update-info-container {
float: right;
- min-width:190px;
+ min-width: 190px;
}
+
.post-update-info {
- display:inline-block;
- float:right;
- width:190px;
- margin-bottom:5px;
+ display: inline-block;
+ float: right;
+ width: 190px;
+ margin-bottom: 5px;
}
+
.post-update-info p {
- font-size:11px;
- line-height:15px;
- margin:0 0 4px 0;
- padding:0;
+ font-size: 11px;
+ line-height: 15px;
+ margin: 0 0 4px 0;
+ padding: 0;
}
+
.post-update-info img {
float: left;
width: 32px;
margin: 4px 8px 0 0;
}
+
.comments-container {
- clear:both;
+ clear: both;
}
+
.admin {
- background-color:#fff380;/* nice yellow */
+ background-color: #fff380; /* nice yellow */
border: 1px solid darkred;
padding: 0 5px 0 5px;
}
+
.admin p {
margin-bottom: 3px;
}
+
.admin #action_status {
- text-align:center;
- font-weight:bold;
+ text-align: center;
+ font-weight: bold;
}
+
#tagSelector {
padding-bottom: 2px;
}
+
#hideIgnoredTagsControl {
margin: 5px 0 0 0;
}
+
#hideIgnoredTagsCb {
margin: 0 2px 0 1px;
}
+
#recaptcha_widget_div {
- width:318px;
- float:left;
- clear:both;
+ width: 318px;
+ float: left;
+ clear: both;
}
+
p.signup_p {
margin: 20px 0px 0px 0px;
}
+
.simple-subscribe-options ul {
- list-style:none;
- list-style-position:outside;
- margin:0;
+ list-style: none;
+ list-style-position: outside;
+ margin: 0;
}
diff --git a/forum/skins/default/templates/ask.html b/forum/skins/default/templates/ask.html
index 84253be5..083b01d9 100644
--- a/forum/skins/default/templates/ask.html
+++ b/forum/skins/default/templates/ask.html
@@ -115,13 +115,6 @@
<p class="title-desc">
{{ form.tags.help_text }}
</p>
- <p class="form-item">
- <strong>{{ form.categories.label_tag }}:</strong> {% trans "(required)" %} <span class="form-error"></span><br>
- {{ form.categories }} {{ form.categories.errors }}
- </p>
- <p class="title-desc">
- {{ form.categories.help_text }}
- </p>
{% if not request.user.is_authenticated %}
<input type="submit" value="{% trans "Login/signup to post your question" %}" class="submit" />
{% else %}
diff --git a/forum/skins/default/templates/authopenid/complete.html b/forum/skins/default/templates/authopenid/complete.html
index 72dca1a5..62970e38 100644
--- a/forum/skins/default/templates/authopenid/complete.html
+++ b/forum/skins/default/templates/authopenid/complete.html
@@ -92,7 +92,6 @@ parameters:
{% endif %}
{{ form1.email }}
</div>
- <p class='nomargin'>{% trans "Tag filter tool will be your right panel, once you log in." %}</p>
<p>{% trans "receive updates motivational blurb" %}</p>
<div class='simple-subscribe-options'>
{{email_feeds_form.subscribe}}
diff --git a/forum/skins/default/templates/authopenid/confirm_email.txt b/forum/skins/default/templates/authopenid/confirm_email.txt
index 0b3b2505..3a01f146 100644
--- a/forum/skins/default/templates/authopenid/confirm_email.txt
+++ b/forum/skins/default/templates/authopenid/confirm_email.txt
@@ -1,13 +1,13 @@
-Gracias por registrarse en Hasked.com
+{% load i18n %}
+{% trans "Thank you for registering at our Q&A forum!" %}
-Los detalles de su cuenta son:
+{% trans "Your account details are:" %}
-Nombre de usuario: {{ username }}
-Contraseña: {{ password }}
+{% trans "Username:" %} {{ username }}
+{% trans "Password:" %} {{ password }}
{% trans "Please sign in here:" %}
{{signup_url}}
-Saludos,
-El equipo administrador de Hasked.com
-
+{% blocktrans %}Sincerely,
+Forum Administrator{% endblocktrans %}
diff --git a/forum/skins/default/templates/authopenid/email_validation.txt b/forum/skins/default/templates/authopenid/email_validation.txt
index d741614f..5b166a9b 100644
--- a/forum/skins/default/templates/authopenid/email_validation.txt
+++ b/forum/skins/default/templates/authopenid/email_validation.txt
@@ -1,13 +1,15 @@
-Saludos de Hasked.com,
+{% load i18n %}
+{% trans "Greetings from the Q&A forum" %},
-Para poder usar Hasked haga click en el siguiente link:
+{% trans "To make use of the Forum, please follow the link below:" %}
{{validation_link}}
-Seguir el link de arriba nos ayuda a verificar su correo electrónico.
+{% trans "Following the link above will help us verify your email address." %}
-Si cree que este mensaje se mandó por error no se requiere de mas acciones.
-Solo ignore este correo, pedimos disculpas por cualquier incoveniente
+{% blocktrans %}If you beleive that this message was sent in mistake -
+no further action is needed. Just ingore this email, we apologize
+for any inconvenience{% endblocktrans %}
-Saludos,
-Equipo de administración de Hasked.com
+{% blocktrans %}Sincerely,
+Forum Administrator{% endblocktrans %}
diff --git a/forum/skins/default/templates/authopenid/sendpw_email.txt b/forum/skins/default/templates/authopenid/sendpw_email.txt
index c4910d12..f044ca45 100644
--- a/forum/skins/default/templates/authopenid/sendpw_email.txt
+++ b/forum/skins/default/templates/authopenid/sendpw_email.txt
@@ -5,5 +5,5 @@ If it were not you, it is safe to ignore this email.{% endblocktrans %}
{% blocktrans %}email explanation how to use new {{password}} for {{username}}
with the {{key_link}}{% endblocktrans %}
-Saludos,
-El Equipo administrador de Hasked.com
+{% blocktrans %}Sincerely,
+Forum Administrator{% endblocktrans %}
diff --git a/forum/skins/default/templates/base.html b/forum/skins/default/templates/base.html
index 58ef0627..3a1848ef 100755
--- a/forum/skins/default/templates/base.html
+++ b/forum/skins/default/templates/base.html
@@ -17,13 +17,12 @@
<link href="{% media "/media/style/style.css" %}" rel="stylesheet" type="text/css" />
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">google.load("jquery", "1.2.6");</script>
- <script type='text/javascript' src='{% href "/content/js/com.cnprog.i18n.js" %}'></script>
- <script type='text/javascript' src='{% href "/content/js/jquery.i18n.js" %}'></script>
- <script type='text/javascript' src='{% href "/content/js/com.cnprog.utils.js" %}'></script>
<script type="text/javascript">
+ /* <![CDATA[ */
var i18nLang = '{{settings.LANGUAGE_CODE}}';
var scriptUrl = '/{{settings.FORUM_SCRIPT_ALIAS}}'
var osqaSkin = '{{settings.OSQA_SKIN}}';
+ /* ]] */
</script>
<script type='text/javascript' src='{% media "/media/js/com.cnprog.i18n.js" %}'></script>
<script type='text/javascript' src='{% media "/media/js/jquery.i18n.js" %}'></script>
@@ -33,10 +32,12 @@
body { margin-top:2.4em; }
</style>
<script type="text/javascript">
+ /* <![CDATA[ */
$(document).ready(function() {
$('#validate_email_alert').click(function(){notify.close(true)})
notify.show();
});
+ /* ]] */
</script>
{% endif %}
diff --git a/forum/skins/default/templates/header.html b/forum/skins/default/templates/header.html
index 3b29ffc4..3afc46c5 100644
--- a/forum/skins/default/templates/header.html
+++ b/forum/skins/default/templates/header.html
@@ -63,4 +63,3 @@
</div>
</div>
<!-- end template header.html -->
-
diff --git a/forum/skins/default/templates/index.html b/forum/skins/default/templates/index.html
index 30cba1be..5bbb192b 100644..100755
--- a/forum/skins/default/templates/index.html
+++ b/forum/skins/default/templates/index.html
@@ -1,165 +1,124 @@
-{% extends "base.html" %}
-<!-- index.html -->
-{% load i18n %}
-{% load extra_tags %}
-{% load humanize %}
-{% load extra_filters %}
-{% load smart_if %}
-{% block title %}{% spaceless %}{% trans "Home" %}{% endspaceless %}{% endblock %}
-{% block meta %}<meta name="keywords" content="{{ settings.APP_KEYWORDS }}" />
- <meta name="description" content="{{ settings.APP_DESCRIPTION }}" />{% endblock %}
-{% block forejs %}
- <script type="text/javascript">
- var tags = {{ tags_autocomplete|safe }};
- $().ready(function(){
- var tab_id = "{{ tab_id }}";
- $("#"+tab_id).attr('className',"on");
- $("#nav_questions").attr('className',"on");
- });
- </script>
- <script type='text/javascript' src='{% media "/media/js/com.cnprog.editor.js" %}'></script>
- <script type='text/javascript' src='{% media "/media/js/com.cnprog.tag_selector.js" %}'></script>
-{% endblock %}
-{% block content %}
-<div class="tabBar">
- <div class="headQuestions">{% trans "Questions" %}</div>
- <div class="tabsA">
- <a id="latest" href="{% url questions %}?sort=latest" title="{% trans "last updated questions" %}" >{% trans "newest" %}</a>
- <a id="active" href="{% url questions %}?sort=active" title="{% trans "most recently updated questions" %}">{% trans "active" %}</a>
- <a id="hottest" href="{% url questions %}?sort=hottest" title="{% trans "hottest questions" %}" >{% trans "hottest" %}</a>
- <a id="mostvoted" href="{% url questions %}?sort=mostvoted" title="{% trans "most voted questions" %}" >{% trans "most voted" %}</a>
- <!--<a id="all" href="{% url questions %}" title="{% trans "all questions" %}" >{% trans "all questions" %}</a>-->
- </div>
-</div>
-<!-- 问题列表 -->
-<div id="listA">
- {% for question in questions %}
- <div class="qstA">
- <h2>
- <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
- </h2>
- <div class="stat">
- <table>
- <tr>
- <td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
- <td><span class="num">{{ question.score|intcomma }}</span> </td>
- <td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
- </tr>
- <tr>
- <td><span class="unit">{% trans "answers" %}</span></td>
- <td><span class="unit">{% trans "votes" %}</span></td>
- <td><span class="unit">{% trans "views" %}</span></td>
- </tr>
- </table>
- </div>
-
- <div class="summary">
- {{ question.summary }}...
- </div>
-
- {% ifequal tab_id 'active'%}
- {% if question.wiki and settings.WIKI_ON %}
- <span class="from wiki">{% trans "community wiki" %}</span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
- {% else %}
- <div class="from">
- {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
- <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
- <span class="score">{% get_score_badge question.last_activity_by %} </span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
- </div>
- {% endif %}
- {% else %}
- {% if question.wiki and settings.WIKI_ON %}
- <span class="from wiki">{% trans "community wiki" %}</span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
- {% else %}
- <div class="from">
- {% comment %}{% gravatar question.author 24 %}{% endcomment %}
- {% if question.last_activity_at != question.added_at %}
- {% if question.author.id != question.last_activity_by.id %}
- {% trans "Posted:" %}
- <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
- <span class="score">{% get_score_badge question.author %} </span>
- / {% trans "Updated:" %}
- <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
- <span class="score">{% get_score_badge question.last_activity_by %} </span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
- {% else %}
- {% trans "Updated:" %}
- <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
- <span class="score">{% get_score_badge question.last_activity_by %} </span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
- {% endif %}
- {% else %}
- {% trans "Posted:" %}
- <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
- <span class="score">{% get_score_badge question.author %} </span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
- {% endif %}
- </div>
- {% endif %}
- {% endifequal %}
-
- <div class="tags">
- {% for tag in question.tagname_list %}
- <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
- {% endfor %}
- </div>
- </div>
- {% endfor %}
-</div>
-{% endblock %}
-
-{% block sidebar %}
-{% if not request.user.is_authenticated %}
-<div class="boxA">
- <h3>{% trans "welcome to website" %}</h3>
- <div class="body">
- {{ settings.APP_INTRO|safe }}
- <div class="more"><a href="{% url about %}">{% trans "about" %} »</a></div>
- <div class="more"><a href="{% url faq %}">{% trans "faq" %} »</a></div>
- </div>
-</div>
-{% else %}
-{% include "tag_selector.html" %}
-{% endif %}
-<div class="boxC">
- <h3>{% trans "Recent tags" %}</h3>
- <div class="body">
- <div class="tags">
- {% for tag in tags %}
- <a rel="tag"
- title="{% blocktrans with tag.name as tagname %}see questions tagged '{{tagname}}'{% endblocktrans %}" href="{% url tag_questions tag.name|urlencode %}">{{ tag.name }}</a>
- {% endfor %}
- </div>
- <div class="more"><a href="{% url tags %}">{% trans "popular tags" %} »</a> </div>
- </div>
-</div>
-{% if awards %}
-<div class="boxC">
- <h3>{% trans "Recent awards" %}</h3>
- <div class="body">
- <ul class="badge-list">
- {% for award in awards %}
- <li>
- <a href="{% url badges %}{{award.badge_id}}/{{award.badge_name}}" title="{{ award.badge_description }}" class="medal">
- <span class="badge{{ award.badge_type }}">&#9679;</span>&nbsp;{{ award.badge_name }}</a> {% trans "given to" %}
- <a href="{% url users %}{{award.user_id}}/{{award.user_name}}">{{ award.user_name }}</a>
- </li>
- {% endfor %}
- </ul>
- <div class="more"><a href="{% url badges %}">{% trans "all awards" %} »</a> </div>
- </div>
-</div>
-{% endif %}
-<div id="feeds">
-<a href="{% media "/feeds/rss" %}" title="{% trans "subscribe to last 30 questions by RSS" %}">{% trans "subscribe to the questions feed" %}</a>
-</div>
-{% endblock %}
-{% block tail %}
-<div style="padding:5px 0 5px 5px;">
-<span class="evenMore">{% trans "Still looking for more? See" %} <a href="{% url questions %}">{% trans "complete list of questions" %}</a> {% trans "or" %} <a href="{% url tags %}">{% trans "popular tags" %}</a>{% trans "." %} {% trans "Please help us answer" %} <a href="{% url questions %}unanswered">{% trans "list of unanswered questions" %}</a>{% trans "." %}</span>
-</div>
-{% endblock %}
-<!-- index.html -->
+{% extends "base.html" %}
+<!-- index.html -->
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+{% load extra_filters %}
+{% load smart_if %}
+{% block title %}{% spaceless %}{% trans "Home" %}{% endspaceless %}{% endblock %}
+{% block meta %}<meta name="keywords" content="{{ settings.APP_KEYWORDS }}" />
+ <meta name="description" content="{{ settings.APP_DESCRIPTION }}" />{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ var tags = {{ tags_autocomplete|safe }};
+ $().ready(function(){
+ var tab_id = "{{ tab_id }}";
+ $("#"+tab_id).attr('className',"on");
+ $("#nav_questions").attr('className',"on");
+ });
+ </script>
+ <script type='text/javascript' src='{% media "/media/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% media "/media/js/com.cnprog.tag_selector.js" %}'></script>
+{% endblock %}
+{% block content %}
+<div class="tabBar">
+ <div class="headQuestions">{% trans "Questions" %}</div>
+ <div class="tabsA">
+ <a id="latest" href="{% url questions %}?sort=latest" title="{% trans "last updated questions" %}" >{% trans "newest" %}</a>
+ <a id="hottest" href="{% url questions %}?sort=hottest" title="{% trans "hottest questions" %}" >{% trans "hottest" %}</a>
+ <a id="mostvoted" href="{% url questions %}?sort=mostvoted" title="{% trans "most voted questions" %}" >{% trans "most voted" %}</a>
+ <a id="all" href="{% url questions %}" title="{% trans "all questions" %}" >{% trans "all questions" %}</a>
+ </div>
+</div>
+<!-- ???? -->
+<div id="listA">
+ {% for question in questions.object_list %}
+ <div class="short-summary">
+ <div class="counts">
+ <div class="votes">
+ <div class="item-count">{{question.score|intcomma}}</div>
+ <div>{% trans "votes" %}</div>
+ </div >
+ <div {% if question.answer_accepted %}title="{% trans "this answer has been accepted to be correct" %}"{% endif %} class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
+ <div class="item-count">{{question.answer_count|intcomma}}</div>
+ <div>{% trans "answers" %}</div>
+ </div>
+ <div class="views">
+ <div class="item-count">{{question.view_count|cnprog_intword|safe}}</div>
+ <div>{% trans "views" %}</div>
+ </div>
+ </div>
+
+ <h2><a title="{{question.summary}}" href="{% url question id=question.id %}{{question.title|slugify}}">{{question.title}}</a></h2>
+
+ <div class="userinfo">
+ <span class="relativetime" title="{{question.last_activity_at}}">{% diff_date question.last_activity_at %}</span>
+ {% if question.last_activity_by %}
+ <a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a> {% get_score_badge question.last_activity_by %}
+ {% endif %}
+ </div>
+
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %} '{{ tag }}' {% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+</div>
+{% endblock %}
+
+{% block sidebar %}
+{% if not request.user.is_authenticated %}
+<div class="boxA">
+ <h3>{% trans "welcome to website" %}</h3>
+ <div class="body">
+ {{ settings.APP_INTRO|safe }}
+ <div class="more"><a href="{% url about %}">{% trans "about" %} </a></div>
+ <div class="more"><a href="{% url faq %}">{% trans "faq" %} </a></div>
+ </div>
+</div>
+{% else %}
+{% include "tag_selector.html" %}
+{% endif %}
+<div class="boxC">
+ <h3>{% trans "Recent tags" %}</h3>
+ <div class="body">
+ <div class="tags">
+ {% for tag in tags %}
+ <a rel="tag"
+ title="{% blocktrans with tag.name as tagname %}see questions tagged '{{tagname}}'{% endblocktrans %}" href="{% url tag_questions tag.name|urlencode %}">{{ tag.name }}</a>
+ {% endfor %}
+ </div>
+ <div class="more"><a href="{% url tags %}">{% trans "popular tags" %} </a> </div>
+ </div>
+</div>
+{% if awards %}
+<div class="boxC">
+ <h3>{% trans "Recent awards" %}</h3>
+ <div class="body">
+ <ul class="badge-list">
+ {% for award in awards %}
+ <li>
+ <a href="{% url badges %}{{award.badge_id}}/{{award.badge_name}}" title="{{ award.badge_description }}" class="medal">
+ <span class="badge{{ award.badge_type }}">&#9679;</span>&nbsp;{{ award.badge_name }}</a> {% trans "given to" %}
+ <a href="{% url users %}{{award.user_id}}/{{award.user_name}}">{{ award.user_name }}</a>
+ </li>
+ {% endfor %}
+ </ul>
+ <div class="more"><a href="{% url badges %}">{% trans "all awards" %} </a> </div>
+ </div>
+</div>
+{% endif %}
+<div id="feeds">
+<a href="{% media "/feeds/rss" %}" title="{% trans "subscribe to last 30 questions by RSS" %}">{% trans "subscribe to the questions feed" %}</a>
+</div>
+{% endblock %}
+{% block tail %}
+<div class="pager">{% cnprog_paginator context %}</div>
+ <div class="pagesize">{% cnprog_pagesize context %}</div>
+<!-- <div style="padding:5px 0 5px 5px;">
+<span class="evenMore">{% trans "Still looking for more? See" %} <a href="{% url questions %}">{% trans "complete list of questions" %}</a> {% trans "or" %} <a href="{% url tags %}">{% trans "popular tags" %}</a>{% trans "." %} {% trans "Please help us answer" %} <a href="{% url questions %}unanswered">{% trans "list of unanswered questions" %}</a>{% trans "." %}</span>
+</div> -->
+{% endblock %}
+<!-- index.html --> \ No newline at end of file
diff --git a/forum/skins/default/templates/index_.html b/forum/skins/default/templates/index_.html
new file mode 100755
index 00000000..5e4cf533
--- /dev/null
+++ b/forum/skins/default/templates/index_.html
@@ -0,0 +1,124 @@
+{% extends "base.html" %}
+<!-- index.html -->
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+{% load extra_filters %}
+{% load smart_if %}
+{% block title %}{% spaceless %}{% trans "Home" %}{% endspaceless %}{% endblock %}
+{% block meta %}<meta name="keywords" content="{{ settings.APP_KEYWORDS }}" />
+ <meta name="description" content="{{ settings.APP_DESCRIPTION }}" />{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ var tags = {{ tags_autocomplete|safe }};
+ $().ready(function(){
+ var tab_id = "{{ tab_id }}";
+ $("#"+tab_id).attr('className',"on");
+ $("#nav_questions").attr('className',"on");
+ });
+ </script>
+ <script type='text/javascript' src='{% media "/media/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% media "/media/js/com.cnprog.tag_selector.js" %}'></script>
+{% endblock %}
+{% block content %}
+<div class="tabBar">
+ <div class="headQuestions">{% trans "Questions" %}</div>
+ <div class="tabsA">
+ <a id="latest" href="{% url index %}?sort=latest" title="{% trans "last updated questions" %}" >{% trans "newest" %}</a>
+ <a id="hottest" href="{% url index %}?sort=hottest" title="{% trans "hottest questions" %}" >{% trans "hottest" %}</a>
+ <a id="mostvoted" href="{% url index %}?sort=mostvoted" title="{% trans "most voted questions" %}" >{% trans "most voted" %}</a>
+ <a id="all" href="{% url index %}" title="{% trans "all questions" %}" >{% trans "all questions" %}</a>
+ </div>
+</div>
+
+<div id="listA">
+ {% for question in questions.object_list %}
+ <div class="short-summary">
+ <div class="counts">
+ <div class="votes">
+ <div class="item-count">{{question.score|intcomma}}</div>
+ <div>{% trans "votes" %}</div>
+ </div >
+ <div {% if question.answer_accepted %}title="{% trans "this answer has been accepted to be correct" %}"{% endif %} class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
+ <div class="item-count">{{question.answer_count|intcomma}}</div>
+ <div>{% trans "answers" %}</div>
+ </div>
+ <div class="views">
+ <div class="item-count">{{question.view_count|cnprog_intword|safe}}</div>
+ <div>{% trans "views" %}</div>
+ </div>
+ </div>
+
+ <h2><a title="{{question.summary}}" href="{% url question id=question.id %}{{question.title|slugify}}">{{question.title}}</a></h2>
+
+ <div class="userinfo">
+ <span class="relativetime" title="{{question.last_activity_at}}">{% diff_date question.last_activity_at %}</span>
+ {% if question.last_activity_by %}
+ <a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a> {% get_score_badge question.last_activity_by %}
+ {% endif %}
+ </div>
+
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %} '{{ tag }}' {% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+</div>
+{% endblock %}
+
+{% block sidebar %}
+{% if not request.user.is_authenticated %}
+<div class="boxA">
+ <h3>{% trans "welcome to website" %}</h3>
+ <div class="body">
+ {{ settings.APP_INTRO|safe }}
+ <div class="more"><a href="{% url about %}">{% trans "about" %} »</a></div>
+ <div class="more"><a href="{% url faq %}">{% trans "faq" %} »</a></div>
+ </div>
+</div>
+{% else %}
+{% include "tag_selector.html" %}
+{% endif %}
+<div class="boxC">
+ <h3>{% trans "Recent tags" %}</h3>
+ <div class="body">
+ <div class="tags">
+ {% for tag in tags %}
+ <a rel="tag"
+ title="{% blocktrans with tag.name as tagname %}see questions tagged '{{tagname}}'{% endblocktrans %}" href="{% url tag_questions tag.name|urlencode %}">{{ tag.name }}</a>
+ {% endfor %}
+ </div>
+ <div class="more"><a href="{% url tags %}">{% trans "popular tags" %} »</a> </div>
+ </div>
+</div>
+{% if awards %}
+<div class="boxC">
+ <h3>{% trans "Recent awards" %}</h3>
+ <div class="body">
+ <ul class="badge-list">
+ {% for award in awards %}
+ <li>
+ <a href="{% url badges %}{{award.badge_id}}/{{award.badge_name}}" title="{{ award.badge_description }}" class="medal">
+ <span class="badge{{ award.badge_type }}">&#9679;</span>&nbsp;{{ award.badge_name }}</a>
+ <a href="{% url users %}{{award.user_id}}/{{award.user_name}}">{{ award.user_name }}</a>
+ </li>
+ {% endfor %}
+ </ul>
+ <div class="more"><a href="{% url badges %}">{% trans "all awards" %} »</a> </div>
+ </div>
+</div>
+{% endif %}
+<div id="feeds">
+<a href="{% media "/feeds/rss" %}" title="{% trans "subscribe to last 30 questions by RSS" %}">{% trans "subscribe to the questions feed" %}</a>
+</div>
+{% endblock %}
+{% block tail %}
+<div class="pager">{% cnprog_paginator context %}</div>
+ <div class="pagesize">{% cnprog_pagesize context %}</div>
+<!-- <div style="padding:5px 0 5px 5px;">
+<span class="evenMore">{% trans "Still looking for more? See" %} <a href="{% url questions %}">{% trans "complete list of questions" %}</a> {% trans "or" %} <a href="{% url tags %}">{% trans "popular tags" %}</a>{% trans "." %} {% trans "Please help us answer" %} <a href="{% url questions %}unanswered">{% trans "list of unanswered questions" %}</a>{% trans "." %}</span>
+</div> -->
+{% endblock %}
+<!-- index.html -->
diff --git a/forum/skins/default/templates/question.html b/forum/skins/default/templates/question.html
index ae562b6b..fe9f5cde 100644
--- a/forum/skins/default/templates/question.html
+++ b/forum/skins/default/templates/question.html
@@ -1,523 +1,3 @@
-<<<<<<< HEAD:templates/question.html
-{% extends "base.html" %}
-<!-- question.html -->
-{% load extra_tags %}
-{% load extra_filters %}
-{% load smart_if %}
-{% load humanize %}
-{% load i18n %}
-{% block title %}{% spaceless %}{{ question.get_question_title }}{% endspaceless %}{% endblock %}
-{% block forejs %}
- <meta name="description" content="{{question.summary}}" />
- <meta name="keywords" content="{{question.tagname_meta_generator}}" />
- <link rel="canonical" href="{{settings.APP_URL}}{{question.get_absolute_url}}" />
- {% if not question.closed %}
- <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
- <script type='text/javascript' src='{% href "/content/js/wmd/showdown.js" %}'></script>
- <script type='text/javascript' src='{% href "/content/js/wmd/wmd.js" %}'></script>
- <link rel="stylesheet" type="text/css" href="{% href "/content/js/wmd/wmd.css" %}" />
- {% endif %}
- <script type='text/javascript' src='{% href "/content/js/com.cnprog.post.js" %}'></script>
- <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
-
- <script type="text/javascript">
- // define reputation needs for comments
- var repNeededForComments = 50;
- $().ready(function(){
- $("#nav_questions").attr('className',"on");
- var answer_sort_tab = "{{ tab_id }}";
- $("#" + answer_sort_tab).attr('className',"on");
-
- Vote.init({{ question.id }}, '{{ question.author.id }}','{{ request.user.id }}');
-
- {% if not question.closed and request.user.is_authenticated %}initEditor();{% endif %}
-
- lanai.highlightSyntax();
- $('#btLogin').bind('click', function(){window.location.href='{% url user_signin %}'; } )
- });
-
- function initEditor(){
- $('#editor').TextAreaResizer();
- //highlight code synctax when editor has new text
- $("#editor").typeWatch({highlight: false, wait: 3000,
- captureLength: 5, callback: lanai.highlightSyntax});
-
- var display = true;
- var txt = "[{% trans "hide preview" %}]";
- $('#pre-collapse').text(txt);
- $('#pre-collapse').bind('click', function(){
- txt = display ? "[{% trans "show preview" %}]" : "[{% trans "hide preview" %}]";
- display = !display;
- $('#previewer').toggle();
- $('#pre-collapse').text(txt);
- });
-
- setupFormValidation("#fmanswer", CPValidator.getQuestionFormRules(), CPValidator.getQuestionFormMessages());
- }
-
- </script>
-{% endblock %}
-
-{% block content %}
-<div class="headNormal">
- <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
-</div>
-<div id="main-body" class="">
- <div id="askform">
- <table style="width:100%;" id="question-table" {% if question.deleted %}class="deleted"{%endif%}>
- <tr>
- <td style="width:30px;vertical-align:top">
- <div class="vote-buttons">
- {% if question_vote %}
- <img id="question-img-upvote-{{ question.id }}" class="question-img-upvote"
- {% if question_vote.is_upvote %}
- src="{% href "/content/images/vote-arrow-up-on.png" %}"
- {% else %}
- src="{% href "/content/images/vote-arrow-up.png" %}"
- {% endif %}
- alt="{% trans "i like this post (click again to cancel)" %}"
- title="{% trans "i like this post (click again to cancel)" %}" />
- <div id="question-vote-number-{{ question.id }}" class="vote-number"
- title="{% trans "current number of votes" %}">
- {{ question.score }}
- </div>
- <img id="question-img-downvote-{{ question.id }}" class="question-img-downvote"
- {% if question_vote.is_downvote %}
- src="{% href "/content/images/vote-arrow-down-on.png" %}"
- {% else %}
- src="{% href "/content/images/vote-arrow-down.png" %}"
- {% endif %}
- alt="{% trans "i dont like this post (click again to cancel)" %}"
- title="{% trans "i dont like this post (click again to cancel)" %}" />
-
- {% else %}
- <img id="question-img-upvote-{{ question.id }}" class="question-img-upvote"
- alt="{% trans "i like this post (click again to cancel)" %}"
- src="{% href "/content/images/vote-arrow-up.png" %}"
- title="{% trans "i like this post (click again to cancel)" %}" />
- <div id="question-vote-number-{{ question.id }}" class="vote-number"
- title="{% trans "current number of votes" %}">
- {{ question.score }}
- </div>
- <img id="question-img-downvote-{{ question.id }}" class="question-img-downvote"
- src="{% href "/content/images/vote-arrow-down.png" %}"
- alt="{% trans "i dont like this post (click again to cancel)" %}"
- title="{% trans "i dont like this post (click again to cancel)" %}" />
-
- {% endif %}
- {% if favorited %}
- <img class="question-img-favorite" src="{% href "/content/images/vote-favorite-on.png" %}"
- alt="{% trans "mark this question as favorite (click again to cancel)" %}"
- title="{% trans "mark this question as favorite (click again to cancel)" %}" />
- <div id="favorite-number" class="favorite-number my-favorite-number">
- {{ question.favourite_count }}
- </div>
- {% else %}
- <img class="question-img-favorite" src="{% href "/content/images/vote-favorite-off.png" %}"
- alt="{% trans "remove favorite mark from this question (click again to restore mark)" %}"
- title="{% trans "remove favorite mark from this question (click again to restore mark)" %}" />
- <div id="favorite-number" class="favorite-number">
- {% ifnotequal question.favourite_count 0 %}{{ question.favourite_count }}{% endifnotequal %}
- </div>
-
- {% endif %}
-
- </div>
- </td>
- <td>
- <div id="item-right">
- <div class="question-body">
- {{ question.html|safe }}
- </div>
- <div id="question-controls" class="post-controls">
- <div id="question-tags" class="tags">
- {% for tag in question.tagname_list %}
- <a href="{% url forum.views.tag tag|urlencode %}" class="post-tag"
- title="{% blocktrans with tag as tagname %}see questions tagged '{{ tagname }}'{% endblocktrans %}" rel="tag">{{ tag }}</a>
- {% endfor %}
- </div>
- {% joinitems using '<span class="action-link-separator">|</span>' %}
- {% if request.user|can_edit_post:question %}
- <span class="action-link"><a href="{% url edit_question question.id %}">{% trans 'edit' %}</a></span>
- {% endif %}
- {% separator %}
- {% if question.closed %}
- {% if request.user|can_reopen_question:question %}
- <span class="action-link"><a href="{% url reopen question.id %}">{% trans "reopen" %}</a></span>
- {% endif %}
- {% else %}
- {% if request.user|can_close_question:question %}
- <span class="action-link"><a href="{% url close question.id %}">{% trans "close" %}</a></span>
- {% endif %}
- {% endif %}
- {% separator %}
- {% if request.user|can_flag_offensive %}
- <span id="question-offensive-flag-{{ question.id }}" class="offensive-flag"
- title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
- <a>{% trans "flag offensive" %}</a>
- {% if request.user|can_view_offensive_flags and question.offensive_flag_count %}
- <span class="darkred">({{ question.offensive_flag_count }})</span>
- {% endif %}
- </span>
- {% endif %}
- {% separator %}
- {% if request.user|can_delete_post:question %}
- <span class="action-link"><a id="question-delete-link-{{question.id}}">{% trans "delete" %}</a></span>
- {% endif %}
- {% endjoinitems %}
- </div>
- <div class="post-update-info-container">
- {% post_contributor_info question "original_author" %}
- {% post_contributor_info question "last_updater" %}
- </div>
- <div class="comments-container" id="comments-container-question-{{question.id}}">
- {% for comment in question.get_comments|slice:":5" %}
- <p class="comment" id="comment-{{comment.id}}">
- {{comment.comment}}
- - <a class="comment-user" href="{{comment.user.get_profile_url}}">{{comment.user}}</a>
- {% spaceless %}
- <span class="comment-age">({% diff_date comment.added_at %})</span>
- {% if request.user|can_delete_comment:comment %}
- <img class="delete-icon"
- src="{% href "/content/images/close-small.png" %}"
- title="{% trans "delete this comment" %}"/>
- {% endif %}
- {% endspaceless %}
- </p>
- {% endfor %}
- </div>
- <div class="post-comments" style="margin-bottom:20px">
- <input id="can-post-comments-question-{{question.id}}" type="hidden" value="{{ request.user|can_add_comments:question }}"/>
- {% if request.user|can_add_comments:question or question.comment_count > 5 %}
- <a id="comments-link-question-{{question.id}}" class="comments-link">
- {% if request.user|can_add_comments:question %}
- {% trans "add comment" %}
- {% endif %}
- {% if question.comment_count > 5 %}
- {% if request.user|can_add_comments:question %}/
- {% blocktrans count question.get_comments|slice:"5:"|length as counter %}
- see <strong>one</strong> more
- {% plural %}
- see <strong>{{counter}}</strong> more
- {% endblocktrans %}
- {% else %}
- {% blocktrans count question.get_comments|slice:"5:"|length as counter %}
- see <strong>one</strong> more comment
- {% plural %}
- see <strong>{{counter}}</strong> more comments
- {% endblocktrans %}
- {% endif %}
- {% endif %}</a>
- {% endif %}
- </div>
- </div>
-
- </td>
- </tr>
- </table>
- {% if question.closed %}
- <div class="question-status" style="margin-bottom:15px">
- <h3>{% blocktrans with question.get_close_reason_display as close_reason %}The question has been closed for the following reason "{{ close_reason }}" by{% endblocktrans %}
- <a href="{{ question.closed_by.get_profile_url }}">{{ question.closed_by.username }}</a>
- {% blocktrans with question.closed_at as closed_at %}close date {{closed_at}}{% endblocktrans %}</h3>
- </div>
- {% endif %}
- {% if answers %}
- <hr/>
- <div class="tabBar">
- <a name="sort-top"></a>
- <div class="headQuestions">
- {% blocktrans count answers|length as counter %}
- One Answer:
- {% plural %}
- {{counter}} Answers:
- {% endblocktrans %}
- </div>
- <div class="tabsA">
- <a id="oldest" href="{% url question question.id %}?sort=oldest#sort-top"
- title="{% trans "oldest answers will be shown first" %}">{% trans "oldest answers" %}</a>
- <a id="latest" href="{% url question question.id %}?sort=latest#sort-top"
- title="{% trans "newest answers will be shown first" %}">{% trans "newest answers" %}</a>
- <a id="votes" href="{% url question question.id %}?sort=votes#sort-top"
- title="{% trans "most voted answers will be shown first" %}">{% trans "popular answers" %}</a>
- </div>
- </div>
- {% cnprog_paginator context %}
-
- {% for answer in answers %}
- <a name="{{ answer.id }}"></a>
- <div id="answer-container-{{ answer.id }}" class="answer {% if answer.accepted %}accepted-answer{% endif %} {% ifequal answer.author_id question.author_id %} answered-by-owner{% endifequal %} {% if answer.deleted %}deleted{% endif %}">
- <table style="width:100%;">
- <tr>
- <td style="width:30px;vertical-align:top">
- <div class="vote-buttons">
- <img id="answer-img-upvote-{{ answer.id }}" class="answer-img-upvote"
- src="{% blockresource %}/content/images/vote-arrow-up{% get_user_vote_image user_answer_votes answer.id 1 %}.png{% endblockresource %}"
- alt="{% trans "i like this answer (click again to cancel)" %}"
- title="{% trans "i like this answer (click again to cancel)" %}"/>
- <div id="answer-vote-number-{{ answer.id }}" class="vote-number" title="{% trans "current number of votes" %}">
- {{ answer.score }}
- </div>
- <img id="answer-img-downvote-{{ answer.id }}" class="answer-img-downvote"
- src="{% blockresource %}/content/images/vote-arrow-down{% get_user_vote_image user_answer_votes answer.id -1 %}.png{% endblockresource %}"
- alt="{% trans "i dont like this answer (click again to cancel)" %}"
- title="{% trans "i dont like this answer (click again to cancel)" %}" />
-
- {% ifequal request.user question.author %}
- <img id="answer-img-accept-{{ answer.id }}" class="answer-img-accept"
- src="{% blockresource %}/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png{% endblockresource %}"
- alt="{% trans "mark this answer as favorite (click again to undo)" %}"
- title="{% trans "mark this answer as favorite (click again to undo)" %}" />
- {% else %}
- {% if answer.accepted %}
- <img id="answer-img-accept-{{ answer.id }}" class="answer-img-accept"
- src="{% blockresource %}/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png{% endblockresource %}"
- alt="{% trans "the author of the question has selected this answer as correct" %}"
- title="{% trans "the author of the question has selected this answer as correct" %}" />
- {% endif %}
- {% endifequal %}
- </div>
- </td>
- <td>
- <div class="item-right">
- <div class="answer-body">
- {{ answer.html|safe }}
- </div>
- <div class="answer-controls post-controls">
- {% joinitems using '<span class="action-link-separator">|</span>' %}
- <span class="linksopt">
- <a href="#{{ answer.id }}" title="{% trans "answer permanent link" %}">
- {% trans "permanent link" %}
- </a>
- </span>
- {% separator %}
- {% if request.user|can_edit_post:answer %}
- <span class="action-link"><a href="{% url edit_answer answer.id %}">{% trans 'edit' %}</a></span>
- {% endif %}
- {% separator %}
-
- {% if request.user|can_flag_offensive %}
- <span id="answer-offensive-flag-{{ answer.id }}" class="offensive-flag"
- title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
- <a>{% trans "flag offensive" %}</a>
- {% if request.user|can_view_offensive_flags and answer.offensive_flag_count %}
- <span class="darkred">({{ answer.offensive_flag_count }})</span>
- {% endif %}
- </span>
- {% endif %}
- {% separator %}
- {% if request.user|can_delete_post:answer %}
- {% spaceless %}
- <span class="action-link">
- <a id="answer-delete-link-{{answer.id}}">
- {% if answer.deleted %}{% trans "undelete" %}{% else %}{% trans "delete" %}{% endif %}</a>
- </span>
- {% endspaceless %}
- {% endif %}
- {% endjoinitems %}
- </div>
- <div class="post-update-info-container">
- {% post_contributor_info answer "original_author" %}
- {% post_contributor_info answer "last_updater" %}
- </div>
- <div class="comments-container" id="comments-container-answer-{{answer.id}}">
- {% for comment in answer.get_comments|slice:":5" %}
- <p id="comment-{{comment.id}}" class="comment">
- {{comment.comment}}
- - <a class="comment-user" href="{{comment.user.get_profile_url}}">{{comment.user}}</a>
- {% spaceless %}
- <span class="comment-age">({% diff_date comment.added_at %})</span>
- {% if request.user|can_delete_comment:comment %}
- <img class="delete-icon"
- src="{% href "/content/images/close-small.png" %}"
- title="{% trans "delete this comment" %}"/>
- {% endif %}
- {% endspaceless %}
- </p>
- {% endfor %}
- </div>
- <div class="post-comments" style="margin-bottom:20px">
- <input id="can-post-comments-answer-{{answer.id}}" type="hidden" value="{{ request.user|can_add_comments:answer}}"/>
- {% if request.user|can_add_comments:answer or answer.comment_count > 5 %}
- <a id="comments-link-answer-{{answer.id}}" class="comments-link">
- {% if request.user|can_add_comments:answer %}
- {% trans "add comment" %}
- {% endif %}
- {% if answer.comment_count > 5 %}
- {% if request.user|can_add_comments:answer %}/
- {% blocktrans count answer.get_comments|slice:"5:"|length as counter %}
- see <strong>one</strong> more
- {% plural %}
- see <strong>{{counter}}</strong> more
- {% endblocktrans %}
- {% else %}
- {% blocktrans count answer.get_comments|slice:"5:"|length as counter %}
- see <strong>one</strong> more comment
- {% plural %}
- see <strong>{{counter}}</strong> more comments
- {% endblocktrans %}
- {% endif %}
- {% endif %}</a>
- {% endif %}
- </div>
- </div>
- <div id="comment-{{ answer.id }}" class="post-comments" >
- <input id="can-post-comments-answer-{{answer.id}}" type="hidden" value="{{ request.user|can_add_comments }}"/>
- <a id="comments-link-answer-{{answer.id}}" class="comments-link">
- {% if answer.comment_count %}{% trans "comments" %}
- <strong>({{answer.comment_count}})</strong>{% else %}{% trans "add comment" %}{% endif %}</a>
- <div id="comments-answer-{{answer.id}}" class="comments-container">
- <div class="comments"/></div>
-
- </td>
- </tr>
- </table>
- </div>
- {% endfor %}
- <div class="paginator-container-left">
- {% cnprog_paginator context %}
- </div>
- {% endif %}
- <form id="fmanswer" action="{% url answer question.id %}" method="post">
- {% if request.user.is_authenticated %}
- <p style="padding-left:3px">
- {{ answer.email_notify }}
- <label for="question-subscribe-updates">
- {% ifequal request.user.get_q_sel_email_feed_frequency 'n' %}
- {% trans "Notify me once a day when there are any new answers" %}
- {% else %}
- {% ifequal request.user.get_q_sel_email_feed_frequency 'd' %}
- {% trans "Notify me once a day when there are any new answers" %}
- {% else %}
- {% ifequal request.user.get_q_sel_email_feed_frequency 'w' %}
- {% trans "Notify me weekly when there are any new answers" %}
- {% endifequal %}
- {% endifequal %}
- {% endifequal %}
- </label>
- {% blocktrans with request.user.get_profile_url as profile_url %}
- You can always adjust frequency of email updates from your {{profile_url}}
- {% endblocktrans %}
- </p>
- {% else %}
- <p style="padding-left:3px">
- <input class="nomargin" type="checkbox" disabled="disabled" />
- <label>{% trans "once you sign in you will be able to subscribe for any updates here" %}</label>
- </p>
- {% endif %}
- <div style="clear:both">
- </div>
-
- {% if not question.closed %}
- <div style="padding:10px 0 0 0;">
- {% spaceless %}
- <div class="headNormal">
- {% if answers %}
- {% trans "Your answer" %}
- {% else %}
- {% trans "Be the first one to answer this question!" %}
- {% endif %}
- </div>
- {% endspaceless %}
- </div>
- {% if not request.user.is_authenticated %}
- <div class="message">{% trans "you can answer anonymously and then login" %}</div>
- {% else %}
- <p class="message">
- {% ifequal request.user question.author %}
- {% trans "answer your own question only to give an answer" %}
- {% else %}
- {% trans "please only give an answer, no discussions" %}
- {% endifequal %}
- </p>
- {% endif %}
-
- <div id="description" class="" >
- <div id="wmd-button-bar" class="wmd-panel"></div>
- {{ answer.text }}
- <div class="preview-toggle">
- <table width="100%">
- <tr>
- <td>
- <span id="pre-collapse"
- title="{% trans "Toggle the real time Markdown editor preview" %}">
- {% trans "toggle preview" %}
- </span>
- </td>
- {% if settings.WIKI_ON %}
- <td style="text-align:right;">
- {{ answer.wiki }}
- <span style="font-weight:normal;cursor:help"
- title="{{answer.wiki.help_text}}">
- {{ answer.wiki.label_tag }}
- </span>
- </td>
- {% endif %}
- </tr>
-
- </table>
- </div>
- <div id="previewer" class="wmd-preview"></div>
- {{ answer.text.errors }}
- </div>
- <p><span class="form-error"></span></p>
- <input type="submit"
- {% if user.is_anonymous %}
- value="{% trans "Login/Signup to Post Your Answer" %}"
- {% else %}
- {% if user == question.author %}
- value="{% trans "Answer Your Own Question" %}"
- {% else %}
- value="{% trans "Answer the question" %}"
- {% endif %}
- {% endif %}
- class="submit" style="float:left"/>
- {% endif %}
- </form>
- </div>
-</div>
-{% endblock %}
-
-{% block sidebar %}
-<div class="boxC">
- <p>
- {% trans "Question tags" %}:
- </p>
- <p class="tags" >
- {% for tag in tags %}
- <a href="{% url forum.views.tag tag.name|urlencode %}"
- title="{% trans "see questions tagged"%}'{{tag.name}}'{% trans "using tags" %}"
- rel="tag">{{ tag.name }}</a> <span class="tag-number">&#215;{{ tag.used_count|intcomma }}</span><br/>
- {% endfor %}
- </p>
- <p>
- {% trans "question asked" %}: <strong title="{{ question.added_at }}">{% diff_date question.added_at %}</strong>
- </p>
- <p>
- {% trans "question was seen" %}: <strong>{{ question.view_count|intcomma }} {% trans "times" %}</strong>
- </p>
- <p>
- {% trans "last updated" %}: <strong title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</strong>
- </p>
-</div>
-
-<div class="boxC">
- <h3 class="subtitle">{% trans "Related questions" %}</h3>
- <div class="questions-related">
- {% for question in similar_questions %}
- <p>
- <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
- </p>
- {% endfor %}
- </div>
-</div>
-
-{% endblock %}
-
-{% block endjs %}
-{% endblock %}
-<!-- end question.html -->
-=======
{% extends "base.html" %}
<!-- question.html -->
{% load extra_tags %}
@@ -1026,4 +506,3 @@
{% block endjs %}
{% endblock %}
<!-- end question.html -->
->>>>>>> evgenyfadeev/master:templates/question.html
diff --git a/forum/skins/default/templates/question_edit.html b/forum/skins/default/templates/question_edit.html
index 5a6268c9..fe711849 100644
--- a/forum/skins/default/templates/question_edit.html
+++ b/forum/skins/default/templates/question_edit.html
@@ -114,12 +114,6 @@
<div class="title-desc">
{{ form.summary.help_text }}
</div>
- <br>
-
- <p class="form-item">
- <strong>{{ form.categories.label_tag }}:</strong> {% trans "(required)" %} <span class="form-error"></span><br>
- {{ form.categories }} {{ form.categories.errors }}
- </p>
<div class="error" ></div>
<input type="submit" value="{% trans "Save edit" %}" class="submit" />
<input type="button" value="{% trans "Cancel" %}" class="submit" onclick="history.back(-1);" />
diff --git a/forum/skins/default/templates/questions.html b/forum/skins/default/templates/questions.html
index 2e593f90..4c3b96d2 100644
--- a/forum/skins/default/templates/questions.html
+++ b/forum/skins/default/templates/questions.html
@@ -1,277 +1,3 @@
-<<<<<<< HEAD:templates/questions.html
-{% extends "base.html" %}
-<!-- questions.html -->
-{% load extra_tags %}
-{% load i18n %}
-{% load humanize %}
-{% load extra_filters %}
-{% load smart_if %}
-{% block title %}{% spaceless %}{% trans "Questions" %}{% endspaceless %}{% endblock %}
-{% block forejs %}
- <script type="text/javascript">
- var tags = {{ tags_autocomplete|safe }};
- $().ready(function(){
- var tab_id = "{{ tab_id }}";
- $("#"+tab_id).attr('className',"on");
- var on_tab = {% if is_unanswered %}'#nav_unanswered'{% else %}'#nav_questions'{% endif %};
- $(on_tab).attr('className','on');
- Hilite.exact = false;
- Hilite.elementid = "listA";
- Hilite.debug_referrer = location.href;
- });
- </script>
- <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
- <script type='text/javascript' src='{% href "/content/js/com.cnprog.tag_selector.js" %}'></script>
-{% endblock %}
-{% block content %}
-<div class="tabBar">
-<<<<<<< HEAD:templates/questions.html
- <div class="headQuestions">{% if searchtag %}{% trans "Found by tags" %}{% else %}{% if searchtitle %}{% trans "Found by title" %}{% else %}{% trans "All questions" %}{% endif %}{% endif %}</div>
-=======
- <div class="headQuestions">
- {% if searchtag %}
- {% trans "Found by tags" %}
- {% else %}
- {% if searchtitle %}
- {% if settings.USE_SPHINX_SEARCH %}
- {% trans "Search results" %}
- {% else %}
- {% trans "Found by title" %}
- {% endif %}
- {% else %}
- {% if is_unanswered %}
- {% trans "Unanswered questions" %}
- {% else %}
- {% trans "All questions" %}
- {% endif %}
- {% endif %}
- {% endif %}
- </div>
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/questions.html
- <div class="tabsA">
- <a id="latest" href="?sort=latest" class="off" title="{% trans "most recently asked questions" %}">{% trans "newest" %}</a>
- <a id="active" href="?sort=active" class="off" title="{% trans "most recently updated questions" %}">{% trans "active" %}</a>
- <a id="hottest" href="?sort=hottest" class="off" title="{% trans "hottest questions" %}">{% trans "hottest" %}</a>
- <a id="mostvoted" href="?sort=mostvoted" class="off" title="{% trans "most voted questions" %}">{% trans "most voted" %}</a>
- </div>
-</div>
-<div id="listA">
- {% for question in questions.object_list %}
- <div class="qstA"
- {% if request.user.is_authenticated %}
- {% if question.interesting_score > 0 %}
- style="background:#ffff99;"
- {% else %}
- {% if not request.user.hide_ignored_questions %}
- {% if question.ignored_score > 0 %}
- style="background:#f3f3f3;"
- {% endif %}
- {% endif %}
- {% endif %}
- {% endif %}
- >
- <h2>
- <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
- </h2>
- <div class="stat">
- <table>
- <tr>
- <td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
- <td><span class="num">{{ question.score|intcomma }}</span> </td>
- <td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
- </tr>
- <tr>
- <td><span class="unit">{% trans "answers" %}</span></td>
- <td><span class="unit">{% trans "votes" %}</span></td>
- <td><span class="unit">{% trans "views" %}</span></td>
- </tr>
- </table>
- </div>
-
- <div class="summary">
- {{ question.summary }}...
- </div>
-
- {% ifequal tab_id 'active'%}
- {% if question.wiki and settings.WIKI_ON %}
- <span class="from wiki">{% trans "community wiki" %}</span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
- {% else %}
- <div class="from">
- {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
- <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
- <span class="score">{% get_score_badge question.last_activity_by %} </span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
- </div>
- {% endif %}
- {% else %}
- {% if question.wiki and settings.WIKI_ON %}
- <span class="from wiki">{% trans "community wiki" %}</span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
- {% else %}
- <div class="from">
- {% comment %}{% gravatar question.author 24 %}{% endcomment %}
- {% if question.last_activity_at != question.added_at %}
- {% if question.author.id != question.last_activity_by.id %}
- {% trans "Posted:" %}
- <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
- <span class="score">{% get_score_badge question.author %} </span>
- / {% trans "Updated:" %}
- <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
- <span class="score">{% get_score_badge question.last_activity_by %} </span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
- {% else %}
- {% trans "Updated:" %}
- <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
- <span class="score">{% get_score_badge question.last_activity_by %} </span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
- {% endif %}
- {% else %}
- {% trans "Posted:" %}
- <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
- <span class="score">{% get_score_badge question.author %} </span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
- {% endif %}
- </div>
- {% endif %}
- {% endifequal %}
-
- <div class="tags">
- {% for tag in question.tagname_list %}
- <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
- {% endfor %}
- </div>
- {%trans "Category: "%}<a href="{% url forum.views.category question.category|urlencode %}">{{ question.category}}</a>
- </div>
- {% endfor %}
- {% if searchtitle %}
- {% if questions_count == 0 %}
- <p class="evenMore" style="padding-top:30px;text-align:center;">
- {% trans "Did not find anything?" %}
- {% else %}
- <p class="evenMore" style="padding-left:9px">
- {% trans "Did not find what you were looking for?" %}
- {% endif %}
- <a href="{% url ask %}">{% trans "Please, post your question!" %}</a>
- </p>
- {% endif %}
-</div>
-{% endblock %}
-
-{% block tail %}
- <div class="pager">{% cnprog_paginator context %}</div>
- <div class="pagesize">{% cnprog_pagesize context %}</div>
-{% endblock %}
-
-{% block sidebar %}
-<div class="boxC">
- {% if searchtag %}
-<<<<<<< HEAD:templates/questions.html
- {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num and searchtag as tagname %}
- have total {{q_num}} questions tagged {{tagname}}
- {% plural %}
- have total {{q_num}} questions tagged {{tagname}}
- {% endblocktrans %}
- {% else %}
- {% if searchtitle %}
- {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %}
- have total {{q_num}} questions containing {{searchtitle}}
- {% plural %}
- have total {{q_num}} questions containing {{searchtitle}}
- {% endblocktrans %}
- {% else %}
- {% blocktrans count questions as cnt with questions_count|intcomma as q_num %}
- have total {{q_num}} questions
- {% plural %}
- have total {{q_num}} questions
- {% endblocktrans %}
- {% endif %}
- {% endif %}
- <p>
-=======
- {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num and searchtag as tagname %}
- have total {{q_num}} questions tagged {{tagname}}
- {% plural %}
- have total {{q_num}} questions tagged {{tagname}}
- {% endblocktrans %}
- {% else %}
- {% if searchtitle %}
- {% if settings.USE_SPHINX_SEARCH %}
- {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %}
- have total {{q_num}} questions containing {{searchtitle}} in full text
- {% plural %}
- have total {{q_num}} questions containing {{searchtitle}} in full text
- {% endblocktrans %}
- {% else %}
- {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %}
- have total {{q_num}} questions containing {{searchtitle}}
- {% plural %}
- have total {{q_num}} questions containing {{searchtitle}}
- {% endblocktrans %}
- {% endif %}
- {% else %}
- {% if is_unanswered %}
- {% blocktrans count questions as cnt with questions_count|intcomma as q_num %}
- have total {{q_num}} unanswered questions
- {% plural %}
- have total {{q_num}} unanswered questions
- {% endblocktrans %}
- {% else %}
- {% blocktrans count questions as cnt with questions_count|intcomma as q_num %}
- have total {{q_num}} questions
- {% plural %}
- have total {{q_num}} questions
- {% endblocktrans %}
- {% endif %}
- {% endif %}
- {% endif %}
- <p class="nomargin">
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/questions.html
- {% ifequal tab_id "latest" %}
- {% trans "latest questions info" %}
- {% endifequal %}
-
- {% ifequal tab_id "active" %}
- {% trans "Questions are sorted by the <strong>time of last update</strong>." %}
- {% trans "Most recently answered ones are shown first." %}
- {% endifequal %}
-
- {% ifequal tab_id "hottest" %}
- {% trans "Questions sorted by <strong>number of responses</strong>." %}
- {% trans "Most answered questions are shown first." %}
- {% endifequal %}
-
- {% ifequal tab_id "mostvoted" %}
- {% trans "Questions are sorted by the <strong>number of votes</strong>." %}
- {% trans "Most voted questions are shown first." %}
- {% endifequal %}
- </p>
-</div>
-{% if request.user.is_authenticated %}
-{% include "tag_selector.html" %}
-{% endif %}
-<div class="boxC">
- <h3 class="subtitle">{% trans "Related tags" %}</h3>
- <div class="tags">
- {% for tag in tags %}
-<<<<<<< HEAD:templates/questions.html
- <a rel="tag" title="{% trans "see questions tagged" %}'{{ tag.name }}'{% trans "using tags" %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
- <span class="tag-number">&#215; {{ tag.used_count|intcomma }}</span>
- <br />
- {% endfor %}
- <br />
-=======
- <a rel="tag" title="{% blocktrans with tag.name as tag_name %}see questions tagged '{{ tag_name }}'{% endblocktrans %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
- <span class="tag-number">&#215; {{ tag.used_count|intcomma }}</span>
- <br />
- {% endfor %}
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/questions.html
- </div>
-</div>
-
-{% endblock %}
-<!-- end questions.html -->
-=======
{% extends "base.html" %}
<!-- questions.html -->
{% load extra_tags %}
@@ -507,4 +233,3 @@
{% endblock %}
<!-- end questions.html -->
->>>>>>> evgenyfadeev/master:templates/questions.html
diff --git a/forum/skins/default/templates/user_email_subscriptions.html b/forum/skins/default/templates/user_email_subscriptions.html
index 10440529..c0204cbc 100644
--- a/forum/skins/default/templates/user_email_subscriptions.html
+++ b/forum/skins/default/templates/user_email_subscriptions.html
@@ -13,12 +13,9 @@
{% endif %}
<form method="POST">
{% include "edit_user_email_feeds_form.html" %}
-<<<<<<< HEAD:templates/user_email_subscriptions.html
-=======
<table class='form-as-table'>
{{tag_filter_selection_form}}
</table>
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/user_email_subscriptions.html
<div class="submit-row text-align-right">
<input type="submit" class="submit" name="save" value="{% trans "Update" %}"/>
<input type="submit" class="submit" name="stop_email" value="{% trans "Stop sending email" %}"/>
diff --git a/forum/skins/default/templates/user_info.html b/forum/skins/default/templates/user_info.html
index 4ebcddd6..c550e13f 100644
--- a/forum/skins/default/templates/user_info.html
+++ b/forum/skins/default/templates/user_info.html
@@ -19,7 +19,7 @@
<tr>
<td align="center">
<div class="scoreNumber">{{view_user.reputation|intcomma}}</div>
- <p><b style="color:#777;">{% trans "karma" %}</b></p>
+ <p><b style="color:#777;">{% trans "reputation" %}</b></p>
</td>
</tr>
</table>
diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py
index 865cd33d..3644fdc3 100644
--- a/forum/templatetags/extra_filters.py
+++ b/forum/templatetags/extra_filters.py
@@ -1,8 +1,4 @@
from django import template
-<<<<<<< HEAD:forum/templatetags/extra_filters.py
-=======
-from django.core import serializers
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/templatetags/extra_filters.py
from forum import auth
import logging
@@ -95,10 +91,3 @@ def cnprog_intword(number):
return number
except:
return number
-<<<<<<< HEAD:forum/templatetags/extra_filters.py
-=======
-
-@register.filter
-def json_serialize(object):
- return serializers.serialize('json',object)
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/templatetags/extra_filters.py
diff --git a/forum/urls.py b/forum/urls.py
index fe335538..fd9ebdc1 100644
--- a/forum/urls.py
+++ b/forum/urls.py
@@ -17,57 +17,59 @@ sitemaps = {
APP_PATH = os.path.dirname(__file__)
urlpatterns = patterns('',
- url(r'^$', app.content.index, name='index'),
- url(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}),
+ url(r'^$', app.readers.index, name='index'),
+ url(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}, name='sitemap'),
#(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/media/images/favicon.ico'}),
#(r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/media/images/favicon.gif'}),
- (r'^m/(?P<path>.*)$', 'django.views.static.serve',
- {'document_root': os.path.join(APP_PATH,'skins').replace('\\','/')}
+ url(r'^m/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': os.path.join(APP_PATH,'skins').replace('\\','/')},
+ name='osqa_media',
),
- (r'^%s(?P<path>.*)$' % _('upfiles/'), 'django.views.static.serve',
- {'document_root': os.path.join(APP_PATH,'upfiles').replace('\\','/')}
+ url(r'^%s(?P<path>.*)$' % _('upfiles/'), 'django.views.static.serve',
+ {'document_root': os.path.join(APP_PATH,'upfiles').replace('\\','/')},
+ name='uploaded_file',
),
- (r'^%s/$' % _('signin/'), 'django_authopenid.views.signin'),
+ url(r'^%s/$' % _('signin/'), 'django_authopenid.views.signin', name='signin'),
url(r'^%s$' % _('about/'), app.meta.about, name='about'),
url(r'^%s$' % _('faq/'), app.meta.faq, name='faq'),
url(r'^%s$' % _('privacy/'), app.meta.privacy, name='privacy'),
url(r'^%s$' % _('logout/'), app.meta.logout, name='logout'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('comments/')), app.content.answer_comments, name='answer_comments'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')), app.content.edit_answer, name='edit_answer'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')), app.content.answer_revisions, name='answer_revisions'),
- url(r'^%s$' % _('questions/'), app.content.questions, name='questions'),
- url(r'^%s%s$' % (_('questions/'), _('ask/')), app.content.ask, name='ask'),
- url(r'^%s%s$' % (_('questions/'), _('unanswered/')), app.content.unanswered, name='unanswered'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.content.edit_question, name='edit_question'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.content.close, name='close'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.content.reopen, name='reopen'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.content.answer, name='answer'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')), app.content.vote, name='vote'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.content.question_revisions, name='question_revisions'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('comments/')), app.content.question_comments, name='question_comments'),
- url(r'^%s$' % _('command/'), app.content.ajax_command, name='call_ajax'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('comments/')), app.writers.answer_comments, name='answer_comments'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')), app.writers.edit_answer, name='edit_answer'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')), app.readers.answer_revisions, name='answer_revisions'),
+ url(r'^%s$' % _('questions/'), app.readers.questions, name='questions'),
+ url(r'^%s%s$' % (_('questions/'), _('ask/')), app.writers.ask, name='ask'),
+ url(r'^%s%s$' % (_('questions/'), _('unanswered/')), app.readers.unanswered, name='unanswered'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.writers.edit_question, name='edit_question'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.commands.close, name='close'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.commands.reopen, name='reopen'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.writers.answer, name='answer'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')), app.commands.vote, name='vote'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.readers.question_revisions, name='question_revisions'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('comments/')), app.writers.question_comments, name='question_comments'),
+ url(r'^%s$' % _('command/'), app.commands.ajax_command, name='call_ajax'),
url(r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('questions/'), _('comments/'),_('delete/')), \
- app.content.delete_comment, kwargs={'commented_object_type':'question'},\
+ app.writers.delete_comment, kwargs={'commented_object_type':'question'},\
name='delete_question_comment'),
url(r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('answers/'), _('comments/'),_('delete/')), \
- app.content.delete_comment, kwargs={'commented_object_type':'answer'}, \
+ app.writers.delete_comment, kwargs={'commented_object_type':'answer'}, \
name='delete_answer_comment'), \
#place general question item in the end of other operations
- url(r'^%s(?P<id>\d+)/' % _('question/'), app.content.question, name='question'),
- url(r'^%s$' % _('tags/'), app.content.tags, name='tags'),
- url(r'^%s(?P<tag>[^/]+)/$' % _('tags/'), app.content.tag, name='tag_questions'),
+ url(r'^%s(?P<id>\d+)/' % _('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%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('interesting/')), app.content.mark_tag, \
+ url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('interesting/')), app.commands.mark_tag, \
kwargs={'reason':'good','action':'add'}, \
name='mark_interesting_tag'),
- url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('ignored/')), app.content.mark_tag, \
+ url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('ignored/')), app.commands.mark_tag, \
kwargs={'reason':'bad','action':'add'}, \
name='mark_ignored_tag'),
- url(r'^%s(?P<tag>[^/]+)/$' % _('unmark-tag/'), app.content.mark_tag, \
+ url(r'^%s(?P<tag>[^/]+)/$' % _('unmark-tag/'), app.commands.mark_tag, \
kwargs={'action':'remove'}, \
name='mark_ignored_tag'),
@@ -77,17 +79,24 @@ urlpatterns = patterns('',
url(r'^%s(?P<id>\d+)//*' % _('users/'), app.users.user, name='user'),
url(r'^%s$' % _('badges/'),app.meta.badges, name='badges'),
url(r'^%s(?P<id>\d+)//*' % _('badges/'), app.meta.badge, name='badge'),
- url(r'^%s%s$' % (_('messages/'), _('markread/')),app.meta.read_message, name='read_message'),
+ url(r'^%s%s$' % (_('messages/'), _('markread/')),app.commands.read_message, name='read_message'),
# (r'^admin/doc/' % _('admin/doc'), include('django.contrib.admindocs.urls')),
- (r'^%s(.*)' % _('nimda/'), admin.site.root),
- url(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
- (r'^%s$' % _('upload/'), app.content.upload),
- url(r'^%s$' % _('books/'), app.books.books, name='books'),
- url(r'^%s%s(?P<short_name>[^/]+)/$' % (_('books/'), _('ask/')), app.books.ask_book, name='ask_book'),
- url(r'^%s(?P<short_name>[^/]+)/$' % _('books/'), app.books.book, name='book'),
- url(r'^%s$' % _('search/'), app.content.search, name='search'),
+ url(r'^%s(.*)' % _('nimda/'), admin.site.root, name='osqa_admin'),
+ url(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}, name='feeds'),
+ url(r'^%s$' % _('upload/'), app.writers.upload, name='upload'),
+ url(r'^%s$' % _('search/'), app.readers.search, name='search'),
url(r'^%s$' % _('feedback/'), app.meta.feedback, name='feedback'),
(r'^%sfb/' % _('account/'), include('fbconnect.urls')),
(r'^%s' % _('account/'), include('django_authopenid.urls')),
(r'^i18n/', include('django.conf.urls.i18n')),
)
+
+from forum.modules import get_modules_script
+
+module_patterns = get_modules_script('urls')
+
+for pattern_file in module_patterns:
+ pattern = getattr(pattern_file, 'urlpatterns', None)
+ if pattern:
+ urlpatterns += pattern
+
diff --git a/user_messages/__init__.py b/forum/user_messages/__init__.py
index 0136c888..0136c888 100644
--- a/user_messages/__init__.py
+++ b/forum/user_messages/__init__.py
diff --git a/user_messages/context_processors.py b/forum/user_messages/context_processors.py
index 894f5801..2bf26269 100644
--- a/user_messages/context_processors.py
+++ b/forum/user_messages/context_processors.py
@@ -6,7 +6,7 @@ Time-stamp: <2008-07-19 23:16:19 carljm context_processors.py>
"""
from django.utils.encoding import StrAndUnicode
-from user_messages import get_and_delete_messages
+from forum.user_messages import get_and_delete_messages
def user_messages (request):
"""
diff --git a/pgfulltext/__init__.py b/forum/utils/__init__.py
index e69de29b..e69de29b 100644
--- a/pgfulltext/__init__.py
+++ b/forum/utils/__init__.py
diff --git a/utils/cache.py b/forum/utils/cache.py
index 410c0662..410c0662 100644
--- a/utils/cache.py
+++ b/forum/utils/cache.py
diff --git a/utils/decorators.py b/forum/utils/decorators.py
index e4e7acb3..e4e7acb3 100644
--- a/utils/decorators.py
+++ b/forum/utils/decorators.py
diff --git a/forum/diff.py b/forum/utils/diff.py
index d741d788..d741d788 100644
--- a/forum/diff.py
+++ b/forum/utils/diff.py
diff --git a/utils/forms.py b/forum/utils/forms.py
index c54056ca..c54056ca 100644
--- a/utils/forms.py
+++ b/forum/utils/forms.py
diff --git a/utils/html.py b/forum/utils/html.py
index 25a74a4a..25a74a4a 100644
--- a/utils/html.py
+++ b/forum/utils/html.py
diff --git a/utils/lists.py b/forum/utils/lists.py
index bbcfae98..bbcfae98 100644
--- a/utils/lists.py
+++ b/forum/utils/lists.py
diff --git a/utils/odict.py b/forum/utils/odict.py
index 2c8391d7..2c8391d7 100644
--- a/utils/odict.py
+++ b/forum/utils/odict.py
diff --git a/forum/views.py b/forum/views.py
deleted file mode 100644
index 2ca4202e..00000000
--- a/forum/views.py
+++ /dev/null
@@ -1,2410 +0,0 @@
-# encoding:utf-8
-import calendar
-from django.conf import settings
-from django.contrib.auth.decorators import login_required
-from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
-from django.core.paginator import Paginator, EmptyPage, InvalidPage
-from django.template import RequestContext, loader
-from django.utils.html import *
-from django.utils import simplejson
-from django.core import serializers
-from django.core.mail import mail_admins
-from django.db import transaction
-from django.db.models import Count, Q
-from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import ugettext as _
-from django.utils.datastructures import SortedDict
-from django.template.defaultfilters import slugify
-from django.core.exceptions import PermissionDenied
-
-from utils.html import sanitize_html
-from utils.decorators import ajax_method, ajax_login_required
-from markdown2 import Markdown
-import os.path
-import random
-import time
-
-import datetime
-from forum import auth
-from forum.auth import *
-from forum.const import *
-from forum.diff import textDiff as htmldiff
-from forum.forms import *
-from forum.models import *
-from forum.user import *
-from forum import auth
-from utils.forms import get_next_url
-
-# used in index page
-INDEX_PAGE_SIZE = 20
-INDEX_AWARD_SIZE = 15
-INDEX_TAGS_SIZE = 100
-# used in tags list
-DEFAULT_PAGE_SIZE = 60
-# used in questions
-QUESTIONS_PAGE_SIZE = 10
-# used in users
-USERS_PAGE_SIZE = 35
-# used in answers
-ANSWERS_PAGE_SIZE = 10
-markdowner = Markdown(html4tags=True)
-question_type = ContentType.objects.get_for_model(Question)
-answer_type = ContentType.objects.get_for_model(Answer)
-comment_type = ContentType.objects.get_for_model(Comment)
-question_revision_type = ContentType.objects.get_for_model(QuestionRevision)
-answer_revision_type = ContentType.objects.get_for_model(AnswerRevision)
-repute_type = ContentType.objects.get_for_model(Repute)
-question_type_id = question_type.id
-answer_type_id = answer_type.id
-comment_type_id = comment_type.id
-question_revision_type_id = question_revision_type.id
-answer_revision_type_id = answer_revision_type.id
-repute_type_id = repute_type.id
-def _get_tags_cache_json():
- tags = Tag.objects.filter(deleted=False).all()
- tags_list = []
- for tag in tags:
- dic = {'n': tag.name, 'c': tag.used_count}
- tags_list.append(dic)
- tags = simplejson.dumps(tags_list)
- return tags
-
-def _get_and_remember_questions_sort_method(request, view_dic, default):
- if default not in view_dic:
- raise Exception('default value must be in view_dic')
-
- q_sort_method = request.REQUEST.get('sort', None)
- if q_sort_method == None:
- q_sort_method = request.session.get('questions_sort_method', default)
-
- if q_sort_method not in view_dic:
- q_sort_method = default
- request.session['questions_sort_method'] = q_sort_method
- return q_sort_method, view_dic[q_sort_method]
-
-def index(request):
- view_dic = {
- "latest":"-last_activity_at",
- "hottest":"-answer_count",
- "mostvoted":"-score",
- }
- view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest')
-
- page_size = request.session.get('pagesize', QUESTIONS_PAGE_SIZE)
- questions = Question.objects.exclude(deleted=True).order_by(orderby)[:page_size]
- # RISK - inner join queries
- questions = questions.select_related()
- tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE)
-
- awards = Award.objects.get_recent_awards()
-
- (interesting_tag_names, ignored_tag_names) = (None, None)
- if request.user.is_authenticated():
- pt = MarkedTag.objects.filter(user=request.user)
- interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
- ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
-
- tags_autocomplete = _get_tags_cache_json()
-
- return render_to_response('index.html', {
- 'interesting_tag_names': interesting_tag_names,
- 'tags_autocomplete': tags_autocomplete,
- 'ignored_tag_names': ignored_tag_names,
- "questions" : questions,
- "tab_id" : view_id,
- "tags" : tags,
- "awards" : awards[:INDEX_AWARD_SIZE],
- }, context_instance=RequestContext(request))
-
-def about(request):
- return render_to_response('about.html', context_instance=RequestContext(request))
-
-def faq(request):
- data = {
- 'gravatar_faq_url': reverse('faq') + '#gravatar',
- 'send_email_key_url': reverse('send_email_key'),
- 'ask_question_url': reverse('ask'),
- }
- return render_to_response('faq.html', data, context_instance=RequestContext(request))
-
-def feedback(request):
- data = {}
- form = None
- if request.method == "POST":
- form = FeedbackForm(request.POST)
- if form.is_valid():
- if not request.user.is_authenticated:
- data['email'] = form.cleaned_data.get('email',None)
- data['message'] = form.cleaned_data['message']
- data['name'] = form.cleaned_data.get('name',None)
- message = render_to_response('feedback_email.txt',data,context_instance=RequestContext(request))
- mail_admins(_('Q&A forum feedback'), message)
- msg = _('Thanks for the feedback!')
- request.user.message_set.create(message=msg)
- return HttpResponseRedirect(get_next_url(request))
- else:
- form = FeedbackForm(initial={'next':get_next_url(request)})
-
- data['form'] = form
- return render_to_response('feedback.html', data, context_instance=RequestContext(request))
-feedback.CANCEL_MESSAGE=_('We look forward to hearing your feedback! Please, give it next time :)')
-
-def privacy(request):
- return render_to_response('privacy.html', context_instance=RequestContext(request))
-
-def unanswered(request):
- return questions(request, unanswered=True)
-
-def questions(request, tagname=None, unanswered=False):
- """
- List of Questions, Tagged questions, and Unanswered questions.
- """
- # template file
- # "questions.html" or maybe index.html in the future
- template_file = "questions.html"
- # get pagesize from session, if failed then get default value
- pagesize = request.session.get("pagesize", 10)
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- view_id, orderby = _get_and_remember_questions_sort_method(request,view_dic,'latest')
-
- # check if request is from tagged questions
- qs = Question.objects.exclude(deleted=True)
-
- if tagname is not None:
- qs = qs.filter(tags__name = unquote(tagname))
-
- if unanswered:
- qs = qs.exclude(answer_accepted=True)
-
- author_name = None
- #user contributed questions & answers
- if 'user' in request.GET:
- try:
- author_name = request.GET['user']
- u = User.objects.get(username=author_name)
- qs = qs.filter(Q(author=u) | Q(answers__author=u))
- except User.DoesNotExist:
- author_name = None
-
- if request.user.is_authenticated():
- uid_str = str(request.user.id)
- qs = qs.extra(
- select = SortedDict([
- (
- 'interesting_score',
- 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
- + 'WHERE forum_markedtag.user_id = %s '
- + 'AND forum_markedtag.tag_id = question_tags.tag_id '
- + 'AND forum_markedtag.reason = \'good\' '
- + 'AND question_tags.question_id = question.id'
- ),
- ]),
- select_params = (uid_str,),
- )
- if request.user.hide_ignored_questions:
- ignored_tags = Tag.objects.filter(user_selections__reason='bad',
- user_selections__user = request.user)
- qs = qs.exclude(tags__in=ignored_tags)
- else:
- qs = qs.extra(
- select = SortedDict([
- (
- 'ignored_score',
- 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
- + 'WHERE forum_markedtag.user_id = %s '
- + 'AND forum_markedtag.tag_id = question_tags.tag_id '
- + 'AND forum_markedtag.reason = \'bad\' '
- + 'AND question_tags.question_id = question.id'
- )
- ]),
- select_params = (uid_str, )
- )
-
- qs = qs.select_related(depth=1).order_by(orderby)
-
- objects_list = Paginator(qs, pagesize)
- questions = objects_list.page(page)
-
- # Get related tags from this page objects
- if questions.object_list.count() > 0:
- related_tags = Tag.objects.get_tags_by_questions(questions.object_list)
- else:
- related_tags = None
- tags_autocomplete = _get_tags_cache_json()
-
- # get the list of interesting and ignored tags
- (interesting_tag_names, ignored_tag_names) = (None, None)
- if request.user.is_authenticated():
- pt = MarkedTag.objects.filter(user=request.user)
- interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
- ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
-
- return render_to_response(template_file, {
- "questions" : questions,
- "author_name" : author_name,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "tags_autocomplete" : tags_autocomplete,
- "searchtag" : tagname,
- "is_unanswered" : unanswered,
- "interesting_tag_names": interesting_tag_names,
- 'ignored_tag_names': ignored_tag_names,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'pagesize' : pagesize
- }}, context_instance=RequestContext(request))
-
-def create_new_answer( question=None, author=None,\
- added_at=None, wiki=False,\
- text='', email_notify=False):
-
- html = sanitize_html(markdowner.convert(text))
-
- #create answer
- answer = Answer(
- question=question,
- author=author,
- added_at=added_at,
- wiki=wiki,
- html=html
- )
- if answer.wiki:
- answer.last_edited_by = answer.author
- answer.last_edited_at = added_at
- answer.wikified_at = added_at
-
- answer.save()
-
- #update question data
- question.last_activity_at = added_at
- question.last_activity_by = author
- question.save()
- Question.objects.update_answer_count(question)
-
- #update revision
- AnswerRevision.objects.create(
- answer=answer,
- revision=1,
- author=author,
- revised_at=added_at,
- summary=CONST['default_version'],
- text=text
- )
-
- #set notification/delete
- if email_notify:
- if author not in question.followed_by.all():
- question.followed_by.add(author)
- else:
- #not sure if this is necessary. ajax should take care of this...
- try:
- question.followed_by.remove(author)
- except:
- pass
-
-def create_new_question(title=None, author=None, added_at=None,
- wiki=False, tagnames=None, summary=None,
- text=None):
- """this is not a view
- and maybe should become one of the methods on Question object?
- """
- html = sanitize_html(markdowner.convert(text))
- question = Question(
- title=title,
- author=author,
- added_at=added_at,
- last_activity_at=added_at,
- last_activity_by=author,
- wiki=wiki,
- tagnames=tagnames,
- html=html,
- summary=summary
- )
- if question.wiki:
- question.last_edited_by = question.author
- question.last_edited_at = added_at
- question.wikified_at = added_at
-
- question.save()
-
- # create the first revision
- QuestionRevision.objects.create(
- question=question,
- revision=1,
- title=question.title,
- author=author,
- revised_at=added_at,
- tagnames=question.tagnames,
- summary=CONST['default_version'],
- text=text
- )
- return question
-
-#TODO: allow anynomus user to ask question by providing email and username.
-@login_required
-def ask(request):
- if request.method == "POST":
- form = AskForm(request.POST)
- if form.is_valid():
-
- added_at = datetime.datetime.now()
- title = strip_tags(form.cleaned_data['title'].strip())
- wiki = form.cleaned_data['wiki']
- tagnames = form.cleaned_data['tags'].strip()
- text = form.cleaned_data['text']
- html = sanitize_html(markdowner.convert(text))
- summary = strip_tags(html)[:120]
-
- if request.user.is_authenticated():
- author = request.user
-
- question = create_new_question(
- title=title,
- author=author,
- added_at=added_at,
- wiki=wiki,
- tagnames=tagnames,
- summary=summary,
- text=text
- )
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- request.session.flush()
- session_key = request.session.session_key
- question = AnonymousQuestion(
- session_key=session_key,
- title=title,
- tagnames=tagnames,
- wiki=wiki,
- text=text,
- summary=summary,
- added_at=added_at,
- ip_addr=request.META['REMOTE_ADDR'],
- )
- question.save()
- return HttpResponseRedirect(reverse('user_signin_new_question'))
- else:
- form = AskForm()
-
- tags = _get_tags_cache_json()
- return render_to_response('ask.html', {
- 'form' : form,
- 'tags' : tags,
- 'email_validation_faq_url':reverse('faq') + '#validate',
- }, context_instance=RequestContext(request))
-
-def question(request, id):
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- qsm = request.session.get('questions_sort_method',None)
- if qsm in ('mostvoted','latest'):
- logging.debug('loaded from session ' + qsm)
- if qsm == 'mostvoted':
- view_id = 'votes'
- orderby = '-score'
- else:
- view_id = 'latest'
- orderby = '-added_at'
- else:
- view_id = "votes"
- orderby = "-score"
-
- logging.debug('view_id=' + str(view_id))
-
- question = get_object_or_404(Question, id=id)
- try:
- pattern = r'/%s%s%d/([\w-]+)' % (settings.FORUM_SCRIPT_ALIAS,_('question/'), question.id)
- path_re = re.compile(pattern)
- logging.debug(pattern)
- logging.debug(request.path)
- m = path_re.match(request.path)
- if m:
- slug = m.group(1)
- logging.debug('have slug %s' % slug)
- assert(slug == slugify(question.title))
- else:
- logging.debug('no match!')
- except:
- return HttpResponseRedirect(question.get_absolute_url())
-
- if question.deleted and not can_view_deleted_post(request.user, question):
- raise Http404
- answer_form = AnswerForm(question, request.user)
- answers = Answer.objects.get_answers_from_question(question, request.user, orderby)
- answers = answers.select_related(depth=1)
-
- favorited = question.has_favorite_by_user(request.user)
- if request.user.is_authenticated():
- question_vote = question.votes.select_related().filter(user=request.user)
- else:
- question_vote = None #is this correct?
- if question_vote is not None and question_vote.count() > 0:
- question_vote = question_vote[0]
-
- user_answer_votes = {}
- for vote in question.get_user_votes_in_answers(request.user):
- if not user_answer_votes.has_key(vote.object_id):
- vote_value = -1
- if vote.is_upvote():
- vote_value = 1
- user_answer_votes[answer.id] = vote_value
-
- if answers is not None:
- answers = answers.order_by("-accepted", orderby)
-
- filtered_answers = []
- for answer in answers:
- if answer.deleted == True:
- if answer.author_id == request.user.id:
- filtered_answers.append(answer)
- else:
- filtered_answers.append(answer)
-
- objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE)
- page_objects = objects_list.page(page)
-
- #todo: merge view counts per user and per session
- #1) view count per session
- update_view_count = False
- if 'question_view_times' not in request.session:
- request.session['question_view_times'] = {}
-
- last_seen = request.session['question_view_times'].get(question.id,None)
- updated_when, updated_who = question.get_last_update_info()
-
- if updated_who != request.user:
- if last_seen:
- if last_seen < updated_when:
- update_view_count = True
- else:
- update_view_count = True
-
- request.session['question_view_times'][question.id] = datetime.datetime.now()
-
- if update_view_count:
- question.view_count += 1
- question.save()
-
- #2) question view count per user
- if request.user.is_authenticated():
- try:
- question_view = QuestionView.objects.get(who=request.user, question=question)
- except QuestionView.DoesNotExist:
- question_view = QuestionView(who=request.user, question=question)
- question_view.when = datetime.datetime.now()
- question_view.save()
-
- return render_to_response('question.html', {
- "question": question,
- "question_vote": question_vote,
- "question_comment_count":question.comments.count(),
- "answer": answer_form,
- "answers": page_objects.object_list,
- "user_answer_votes": user_answer_votes,
- "tags": question.tags.all(),
- "tab_id": view_id,
- "favorited": favorited,
- "similar_questions": Question.objects.get_similar_questions(question),
- "context": {
- 'is_paginated': True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': page_objects.has_previous(),
- 'has_next': page_objects.has_next(),
- 'previous': page_objects.previous_page_number(),
- 'next': page_objects.next_page_number(),
- 'base_url': request.path + '?sort=%s&' % view_id,
- 'extend_url': "#sort-top"
- }
- }, context_instance=RequestContext(request))
-
-@login_required
-def close(request, id):
- question = get_object_or_404(Question, id=id)
- if not can_close_question(request.user, question):
- return HttpResponse('Permission denied.')
- if request.method == 'POST':
- form = CloseForm(request.POST)
- if form.is_valid():
- reason = form.cleaned_data['reason']
- question.closed = True
- question.closed_by = request.user
- question.closed_at = datetime.datetime.now()
- question.close_reason = reason
- question.save()
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = CloseForm()
- return render_to_response('close.html', {
- 'form': form,
- 'question': question,
- }, context_instance=RequestContext(request))
-
-@login_required
-def reopen(request, id):
- question = get_object_or_404(Question, id=id)
- # open question
- if not can_reopen_question(request.user, question):
- return HttpResponse('Permission denied.')
- if request.method == 'POST':
- Question.objects.filter(id=question.id).update(closed=False,
- closed_by=None, closed_at=None, close_reason=None)
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- return render_to_response('reopen.html', {
- 'question': question,
- }, context_instance=RequestContext(request))
-
-@login_required
-def edit_question(request, id):
- question = get_object_or_404(Question, id=id)
- if question.deleted and not can_view_deleted_post(request.user, question):
- raise Http404
- if can_edit_post(request.user, question):
- return _edit_question(request, question)
- elif can_retag_questions(request.user):
- return _retag_question(request, question)
- else:
- raise Http404
-
-def _retag_question(request, question):
- if request.method == 'POST':
- form = RetagQuestionForm(question, request.POST)
- if form.is_valid():
- if form.has_changed():
- latest_revision = question.get_latest_revision()
- retagged_at = datetime.datetime.now()
- # Update the Question itself
- Question.objects.filter(id=question.id).update(
- tagnames=form.cleaned_data['tags'],
- last_edited_at=retagged_at,
- last_edited_by=request.user,
- last_activity_at=retagged_at,
- last_activity_by=request.user
- )
- # Update the Question's tag associations
- tags_updated = Question.objects.update_tags(question,
- form.cleaned_data['tags'], request.user)
- # Create a new revision
- QuestionRevision.objects.create(
- question=question,
- title=latest_revision.title,
- author=request.user,
- revised_at=retagged_at,
- tagnames=form.cleaned_data['tags'],
- summary=CONST['retagged'],
- text=latest_revision.text
- )
- # send tags updated singal
- tags_updated.send(sender=question.__class__, question=question)
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = RetagQuestionForm(question)
- return render_to_response('question_retag.html', {
- 'question': question,
- 'form': form,
- 'tags': _get_tags_cache_json(),
- }, context_instance=RequestContext(request))
-
-def _edit_question(request, question):
- latest_revision = question.get_latest_revision()
- revision_form = None
- if request.method == 'POST':
- if 'select_revision' in request.POST:
- # user has changed revistion number
- revision_form = RevisionForm(question, latest_revision, request.POST)
- if revision_form.is_valid():
- # Replace with those from the selected revision
- form = EditQuestionForm(question,
- QuestionRevision.objects.get(question=question,
- revision=revision_form.cleaned_data['revision']))
- else:
- form = EditQuestionForm(question, latest_revision, request.POST)
- else:
- # Always check modifications against the latest revision
- form = EditQuestionForm(question, latest_revision, request.POST)
- if form.is_valid():
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- if form.has_changed():
- edited_at = datetime.datetime.now()
- tags_changed = (latest_revision.tagnames !=
- form.cleaned_data['tags'])
- tags_updated = False
- # Update the Question itself
- updated_fields = {
- 'title': form.cleaned_data['title'],
- 'last_edited_at': edited_at,
- 'last_edited_by': request.user,
- 'last_activity_at': edited_at,
- 'last_activity_by': request.user,
- 'tagnames': form.cleaned_data['tags'],
- 'summary': strip_tags(html)[:120],
- 'html': html,
- }
-
- # only save when it's checked
- # because wiki doesn't allow to be edited if last version has been enabled already
- # and we make sure this in forms.
- if ('wiki' in form.cleaned_data and
- form.cleaned_data['wiki']):
- updated_fields['wiki'] = True
- updated_fields['wikified_at'] = edited_at
-
- Question.objects.filter(
- id=question.id).update(** updated_fields)
- # Update the Question's tag associations
- if tags_changed:
- tags_updated = Question.objects.update_tags(
- question, form.cleaned_data['tags'], request.user)
- # Create a new revision
- revision = QuestionRevision(
- question=question,
- title=form.cleaned_data['title'],
- author=request.user,
- revised_at=edited_at,
- tagnames=form.cleaned_data['tags'],
- text=form.cleaned_data['text'],
- )
- if form.cleaned_data['summary']:
- revision.summary = form.cleaned_data['summary']
- else:
- revision.summary = 'No.%s Revision' % latest_revision.revision
- revision.save()
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
-
- revision_form = RevisionForm(question, latest_revision)
- form = EditQuestionForm(question, latest_revision)
- return render_to_response('question_edit.html', {
- 'question': question,
- 'revision_form': revision_form,
- 'form': form,
- 'tags': _get_tags_cache_json()
- }, context_instance=RequestContext(request))
-
-
-@login_required
-def edit_answer(request, id):
- answer = get_object_or_404(Answer, id=id)
- if answer.deleted and not can_view_deleted_post(request.user, answer):
- raise Http404
- elif not can_edit_post(request.user, answer):
- raise Http404
- else:
- latest_revision = answer.get_latest_revision()
- if request.method == "POST":
- if 'select_revision' in request.POST:
- # user has changed revistion number
- revision_form = RevisionForm(answer, latest_revision, request.POST)
- if revision_form.is_valid():
- # Replace with those from the selected revision
- form = EditAnswerForm(answer,
- AnswerRevision.objects.get(answer=answer,
- revision=revision_form.cleaned_data['revision']))
- else:
- form = EditAnswerForm(answer, latest_revision, request.POST)
- else:
- form = EditAnswerForm(answer, latest_revision, request.POST)
- if form.is_valid():
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- if form.has_changed():
- edited_at = datetime.datetime.now()
- updated_fields = {
- 'last_edited_at': edited_at,
- 'last_edited_by': request.user,
- 'html': html,
- }
- Answer.objects.filter(id=answer.id).update(** updated_fields)
-
- revision = AnswerRevision(
- answer=answer,
- author=request.user,
- revised_at=edited_at,
- text=form.cleaned_data['text']
- )
-
- if form.cleaned_data['summary']:
- revision.summary = form.cleaned_data['summary']
- else:
- revision.summary = 'No.%s Revision' % latest_revision.revision
- revision.save()
-
- answer.question.last_activity_at = edited_at
- answer.question.last_activity_by = request.user
- answer.question.save()
-
- return HttpResponseRedirect(answer.get_absolute_url())
- else:
- revision_form = RevisionForm(answer, latest_revision)
- form = EditAnswerForm(answer, latest_revision)
- return render_to_response('answer_edit.html', {
- 'answer': answer,
- 'revision_form': revision_form,
- 'form': form,
- }, context_instance=RequestContext(request))
-
-QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n'
- '<div class="text">%(html)s</div>\n'
- '<div class="tags">%(tags)s</div>')
-def question_revisions(request, id):
- post = get_object_or_404(Question, id=id)
- revisions = list(post.revisions.all())
- revisions.reverse()
- for i, revision in enumerate(revisions):
- revision.html = QUESTION_REVISION_TEMPLATE % {
- 'title': revision.title,
- 'html': sanitize_html(markdowner.convert(revision.text)),
- 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
- for tag in revision.tagnames.split(' ')]),
- }
- if i > 0:
- revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
- else:
- revisions[i].diff = QUESTION_REVISION_TEMPLATE % {
- 'title': revisions[0].title,
- 'html': sanitize_html(markdowner.convert(revisions[0].text)),
- 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
- for tag in revisions[0].tagnames.split(' ')]),
- }
- revisions[i].summary = _('initial version')
- return render_to_response('revisions_question.html', {
- 'post': post,
- 'revisions': revisions,
- }, context_instance=RequestContext(request))
-
-ANSWER_REVISION_TEMPLATE = ('<div class="text">%(html)s</div>')
-def answer_revisions(request, id):
- post = get_object_or_404(Answer, id=id)
- revisions = list(post.revisions.all())
- revisions.reverse()
- for i, revision in enumerate(revisions):
- revision.html = ANSWER_REVISION_TEMPLATE % {
- 'html': sanitize_html(markdowner.convert(revision.text))
- }
- if i > 0:
- revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
- else:
- revisions[i].diff = revisions[i].text
- revisions[i].summary = _('initial version')
- return render_to_response('revisions_answer.html', {
- 'post': post,
- 'revisions': revisions,
- }, context_instance=RequestContext(request))
-
-@login_required
-def answer(request, id):
- question = get_object_or_404(Question, id=id)
- if request.method == "POST":
- form = AnswerForm(question, request.user, request.POST)
- if form.is_valid():
- wiki = form.cleaned_data['wiki']
- text = form.cleaned_data['text']
- update_time = datetime.datetime.now()
-
- if request.user.is_authenticated():
- create_new_answer(
- question=question,
- author=request.user,
- added_at=update_time,
- wiki=wiki,
- text=text,
- email_notify=form.cleaned_data['email_notify']
- )
- else:
- request.session.flush()
- html = sanitize_html(markdowner.convert(text))
- summary = strip_tags(html)[:120]
- anon = AnonymousAnswer(
- question=question,
- wiki=wiki,
- text=text,
- summary=summary,
- session_key=request.session.session_key,
- ip_addr=request.META['REMOTE_ADDR'],
- )
- anon.save()
- return HttpResponseRedirect(reverse('user_signin_new_answer'))
-
- return HttpResponseRedirect(question.get_absolute_url())
-
-def tags(request):
- stag = ""
- is_paginated = True
- sortby = request.GET.get('sort', 'used')
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- if request.method == "GET":
- stag = request.GET.get("q", "").strip()
- if stag != '':
- objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE)
- else:
- if sortby == "used":
- sortby = "-used_count"
- else:
- sortby = "name"
- objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by(sortby), DEFAULT_PAGE_SIZE)
- try:
- tags = objects_list.page(page)
- except (EmptyPage, InvalidPage):
- tags = objects_list.page(objects_list.num_pages)
-
- return render_to_response('tags.html', {
- "tags" : tags,
- "stag" : stag,
- "tab_id" : sortby,
- "keywords" : stag,
- "context" : {
- 'is_paginated' : is_paginated,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': tags.has_previous(),
- 'has_next': tags.has_next(),
- 'previous': tags.previous_page_number(),
- 'next': tags.next_page_number(),
- 'base_url' : reverse('tags') + '?sort=%s&' % sortby
- }
- }, context_instance=RequestContext(request))
-
-def tag(request, tag):
- return questions(request, tagname=tag)
-
-def vote(request, id):
- """
- vote_type:
- acceptAnswer : 0,
- questionUpVote : 1,
- questionDownVote : 2,
- favorite : 4,
- answerUpVote: 5,
- answerDownVote:6,
- offensiveQuestion : 7,
- offensiveAnswer:8,
- removeQuestion: 9,
- removeAnswer:10
- questionSubscribeUpdates:11
-
- accept answer code:
- response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default
- response_data['success'] = 0, failed 1, Success - by default
- response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel)
-
- vote code:
- allowed = -3, Don't have enough votes left
- -2, Don't have enough reputation score
- -1, Vote his own post
- 0, no allowed - Anonymous
- 1, Allowed - by default
- status = 0, By default
- 1, Cancel
- 2, Vote is too old to be canceled
-
- offensive code:
- allowed = -3, Don't have enough flags left
- -2, Don't have enough reputation score to do this
- 0, not allowed
- 1, allowed
- status = 0, by default
- 1, can't do it again
- """
- response_data = {
- "allowed": 1,
- "success": 1,
- "status": 0,
- "count": 0,
- "message": ''
- }
-
- def can_vote(vote_score, user):
- if vote_score == 1:
- return can_vote_up(request.user)
- else:
- return can_vote_down(request.user)
-
- try:
- if not request.user.is_authenticated():
- response_data['allowed'] = 0
- response_data['success'] = 0
-
- elif request.is_ajax():
- question = get_object_or_404(Question, id=id)
- vote_type = request.POST.get('type')
-
- #accept answer
- if vote_type == '0':
- answer_id = request.POST.get('postId')
- answer = get_object_or_404(Answer, id=answer_id)
- # make sure question author is current user
- if question.author == request.user:
- # answer user who is also question author is not allow to accept answer
- if answer.author == question.author:
- response_data['success'] = 0
- response_data['allowed'] = -1
- # check if answer has been accepted already
- elif answer.accepted:
- onAnswerAcceptCanceled(answer, request.user)
- response_data['status'] = 1
- else:
- # set other answers in this question not accepted first
- for answer_of_question in Answer.objects.get_answers_from_question(question, request.user):
- if answer_of_question != answer and answer_of_question.accepted:
- onAnswerAcceptCanceled(answer_of_question, request.user)
-
- #make sure retrieve data again after above author changes, they may have related data
- answer = get_object_or_404(Answer, id=answer_id)
- onAnswerAccept(answer, request.user)
- else:
- response_data['allowed'] = 0
- response_data['success'] = 0
- # favorite
- elif vote_type == '4':
- has_favorited = False
- fav_questions = FavoriteQuestion.objects.filter(question=question)
- # if the same question has been favorited before, then delete it
- if fav_questions is not None:
- for item in fav_questions:
- if item.user == request.user:
- item.delete()
- response_data['status'] = 1
- response_data['count'] = len(fav_questions) - 1
- if response_data['count'] < 0:
- response_data['count'] = 0
- has_favorited = True
- # if above deletion has not been executed, just insert a new favorite question
- if not has_favorited:
- new_item = FavoriteQuestion(question=question, user=request.user)
- new_item.save()
- response_data['count'] = FavoriteQuestion.objects.filter(question=question).count()
- Question.objects.update_favorite_count(question)
-
- elif vote_type in ['1', '2', '5', '6']:
- post_id = id
- post = question
- vote_score = 1
- if vote_type in ['5', '6']:
- answer_id = request.POST.get('postId')
- answer = get_object_or_404(Answer, id=answer_id)
- post_id = answer_id
- post = answer
- if vote_type in ['2', '6']:
- vote_score = -1
-
- if post.author == request.user:
- response_data['allowed'] = -1
- elif not can_vote(vote_score, request.user):
- response_data['allowed'] = -2
- elif post.votes.filter(user=request.user).count() > 0:
- vote = post.votes.filter(user=request.user)[0]
- # unvote should be less than certain time
- if (datetime.datetime.now().day - vote.voted_at.day) >= VOTE_RULES['scope_deny_unvote_days']:
- response_data['status'] = 2
- else:
- voted = vote.vote
- if voted > 0:
- # cancel upvote
- onUpVotedCanceled(vote, post, request.user)
-
- else:
- # cancel downvote
- onDownVotedCanceled(vote, post, request.user)
-
- response_data['status'] = 1
- response_data['count'] = post.score
- elif Vote.objects.get_votes_count_today_from_user(request.user) >= VOTE_RULES['scope_votes_per_user_per_day']:
- response_data['allowed'] = -3
- else:
- vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
- if vote_score > 0:
- # upvote
- onUpVoted(vote, post, request.user)
- else:
- # downvote
- onDownVoted(vote, post, request.user)
-
- votes_left = VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user)
- if votes_left <= VOTE_RULES['scope_warn_votes_left']:
- response_data['message'] = u'%s votes left' % votes_left
- response_data['count'] = post.score
- elif vote_type in ['7', '8']:
- post = question
- post_id = id
- if vote_type == '8':
- post_id = request.POST.get('postId')
- post = get_object_or_404(Answer, id=post_id)
-
- if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= VOTE_RULES['scope_flags_per_user_per_day']:
- response_data['allowed'] = -3
- elif not can_flag_offensive(request.user):
- response_data['allowed'] = -2
- elif post.flagged_items.filter(user=request.user).count() > 0:
- response_data['status'] = 1
- else:
- item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now())
- onFlaggedItem(item, post, request.user)
- response_data['count'] = post.offensive_flag_count
- # send signal when question or answer be marked offensive
- mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user)
- elif vote_type in ['9', '10']:
- post = question
- post_id = id
- if vote_type == '10':
- post_id = request.POST.get('postId')
- post = get_object_or_404(Answer, id=post_id)
-
- if not can_delete_post(request.user, post):
- response_data['allowed'] = -2
- elif post.deleted == True:
- logging.debug('debug restoring post in view')
- onDeleteCanceled(post, request.user)
- response_data['status'] = 1
- else:
- onDeleted(post, request.user)
- delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user)
- elif vote_type == '11':#subscribe q updates
- user = request.user
- if user.is_authenticated():
- if user not in question.followed_by.all():
- question.followed_by.add(user)
- if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False:
- response_data['message'] = \
- _('subscription saved, %(email)s needs validation, see %(details_url)s') \
- % {'email':user.email,'details_url':reverse('faq') + '#validate'}
- feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type='q_sel')
- if feed_setting.frequency == 'n':
- feed_setting.frequency = 'd'
- feed_setting.save()
- if 'message' in response_data:
- response_data['message'] += '<br/>'
- response_data['message'] = _('email update frequency has been set to daily')
- #response_data['status'] = 1
- #responst_data['allowed'] = 1
- else:
- pass
- #response_data['status'] = 0
- #response_data['allowed'] = 0
- elif vote_type == '12':#unsubscribe q updates
- user = request.user
- if user.is_authenticated():
- if user in question.followed_by.all():
- question.followed_by.remove(user)
- else:
- response_data['success'] = 0
- response_data['message'] = u'Request mode is not supported. Please try again.'
-
- data = simplejson.dumps(response_data)
-
- except Exception, e:
- response_data['message'] = str(e)
- data = simplejson.dumps(response_data)
- return HttpResponse(data, mimetype="application/json")
-
-@ajax_login_required
-def mark_tag(request, tag=None, **kwargs):
- action = kwargs['action']
- ts = MarkedTag.objects.filter(user=request.user, tag__name=tag)
- if action == 'remove':
- logging.debug('deleting tag %s' % tag)
- ts.delete()
- else:
- reason = kwargs['reason']
- if len(ts) == 0:
- try:
- t = Tag.objects.get(name=tag)
- mt = MarkedTag(user=request.user, reason=reason, tag=t)
- mt.save()
- except:
- pass
- else:
- ts.update(reason=reason)
- return HttpResponse(simplejson.dumps(''), mimetype="application/json")
-
-@ajax_login_required
-def ajax_toggle_ignored_questions(request):
- if request.user.hide_ignored_questions:
- new_hide_setting = False
- else:
- new_hide_setting = True
- request.user.hide_ignored_questions = new_hide_setting
- request.user.save()
-
-@ajax_method
-def ajax_command(request):
- if 'command' not in request.POST:
- return HttpResponseForbidden(mimetype="application/json")
- if request.POST['command'] == 'toggle-ignored-questions':
- return ajax_toggle_ignored_questions(request)
-
-def users(request):
- is_paginated = True
- sortby = request.GET.get('sort', 'reputation')
- suser = request.REQUEST.get('q', "")
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- if suser == "":
- if sortby == "newest":
- objects_list = Paginator(User.objects.all().order_by('-date_joined'), USERS_PAGE_SIZE)
- elif sortby == "last":
- objects_list = Paginator(User.objects.all().order_by('date_joined'), USERS_PAGE_SIZE)
- elif sortby == "user":
- objects_list = Paginator(User.objects.all().order_by('username'), USERS_PAGE_SIZE)
- # default
- else:
- objects_list = Paginator(User.objects.all().order_by('-reputation'), USERS_PAGE_SIZE)
- base_url = reverse('users') + '?sort=%s&' % sortby
- else:
- sortby = "reputation"
- objects_list = Paginator(User.objects.extra(where=['username like %s'], params=['%' + suser + '%']).order_by('-reputation'), USERS_PAGE_SIZE)
- base_url = reverse('users') + '?name=%s&sort=%s&' % (suser, sortby)
-
- try:
- users = objects_list.page(page)
- except (EmptyPage, InvalidPage):
- users = objects_list.page(objects_list.num_pages)
-
- return render_to_response('users.html', {
- "users" : users,
- "suser" : suser,
- "keywords" : suser,
- "tab_id" : sortby,
- "context" : {
- 'is_paginated' : is_paginated,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': users.has_previous(),
- 'has_next': users.has_next(),
- 'previous': users.previous_page_number(),
- 'next': users.next_page_number(),
- 'base_url' : base_url
- }
-
- }, context_instance=RequestContext(request))
-
-def user(request, id):
- sort = request.GET.get('sort', 'stats')
- user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0])
- from forum import views
- func = getattr(views, user_view.view_name)
- return func(request, id, user_view)
-
-@login_required
-def moderate_user(request, id):
- """ajax handler of user moderation
- """
- if not auth.can_moderate_users(request.user) or request.method != 'POST':
- raise Http404
- if not request.is_ajax():
- return HttpResponseForbidden(mimetype="application/json")
-
- user = get_object_or_404(User, id=id)
- form = ModerateUserForm(request.POST, instance=user)
-
- if form.is_valid():
- form.save()
- logging.debug('data saved')
- response = HttpResponse(simplejson.dumps(''), mimetype="application/json")
- else:
- response = HttpResponseForbidden(mimetype="application/json")
- return response
-
-@login_required
-def edit_user(request, id):
- user = get_object_or_404(User, id=id)
- if request.user != user:
- raise Http404
- if request.method == "POST":
- form = EditUserForm(user, request.POST)
- if form.is_valid():
- new_email = sanitize_html(form.cleaned_data['email'])
-
- from django_authopenid.views import set_new_email
- set_new_email(user, new_email)
-
- #user.username = sanitize_html(form.cleaned_data['username'])
- user.real_name = sanitize_html(form.cleaned_data['realname'])
- user.website = sanitize_html(form.cleaned_data['website'])
- user.location = sanitize_html(form.cleaned_data['city'])
- user.date_of_birth = sanitize_html(form.cleaned_data['birthday'])
- if len(user.date_of_birth) == 0:
- user.date_of_birth = '1900-01-01'
- user.about = sanitize_html(form.cleaned_data['about'])
-
- user.save()
- # send user updated singal if full fields have been updated
- if user.email and user.real_name and user.website and user.location and \
- user.date_of_birth and user.about:
- user_updated.send(sender=user.__class__, instance=user, updated_by=user)
- return HttpResponseRedirect(user.get_profile_url())
- else:
- form = EditUserForm(user)
- return render_to_response('user_edit.html', {
- 'form' : form,
- 'gravatar_faq_url' : reverse('faq') + '#gravatar',
- }, context_instance=RequestContext(request))
-
-def user_stats(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- questions = Question.objects.extra(
- select={
- 'vote_count' : 'question.score',
- 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
- 'la_user_id' : 'auth_user.id',
- 'la_username' : 'auth_user.username',
- 'la_user_gold' : 'auth_user.gold',
- 'la_user_silver' : 'auth_user.silver',
- 'la_user_bronze' : 'auth_user.bronze',
- 'la_user_reputation' : 'auth_user.reputation'
- },
- select_params=[user_id],
- tables=['question', 'auth_user'],
- where=['question.deleted=False AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'],
- params=[user_id],
- order_by=['-vote_count', '-last_activity_at']
- ).values('vote_count',
- 'favorited_myself',
- 'id',
- 'title',
- 'author_id',
- 'added_at',
- 'answer_accepted',
- 'answer_count',
- 'comment_count',
- 'view_count',
- 'favourite_count',
- 'summary',
- 'tagnames',
- 'vote_up_count',
- 'vote_down_count',
- 'last_activity_at',
- 'la_user_id',
- 'la_username',
- 'la_user_gold',
- 'la_user_silver',
- 'la_user_bronze',
- 'la_user_reputation')[:100]
-
- answered_questions = Question.objects.extra(
- select={
- 'vote_up_count' : 'answer.vote_up_count',
- 'vote_down_count' : 'answer.vote_down_count',
- 'answer_id' : 'answer.id',
- 'accepted' : 'answer.accepted',
- 'vote_count' : 'answer.score',
- 'comment_count' : 'answer.comment_count'
- },
- tables=['question', 'answer'],
- where=['answer.deleted=False AND question.deleted=False AND answer.author_id=%s AND answer.question_id=question.id'],
- params=[user_id],
- order_by=['-vote_count', '-answer_id'],
- select_params=[user_id]
- ).distinct().values('comment_count',
- 'id',
- 'answer_id',
- 'title',
- 'author_id',
- 'accepted',
- 'vote_count',
- 'answer_count',
- 'vote_up_count',
- 'vote_down_count')[:100]
-
- up_votes = Vote.objects.get_up_vote_count_from_user(user)
- down_votes = Vote.objects.get_down_vote_count_from_user(user)
- votes_today = Vote.objects.get_votes_count_today_from_user(user)
- votes_total = VOTE_RULES['scope_votes_per_user_per_day']
-
- question_id_set = set(map(lambda v: v['id'], list(questions))) \
- | set(map(lambda v: v['id'], list(answered_questions)))
-
- user_tags = Tag.objects.filter(questions__id__in = question_id_set)
- try:
- from django.db.models import Count
- awards = Award.objects.extra(
- select={'id': 'badge.id',
- 'name':'badge.name',
- 'description': 'badge.description',
- 'type': 'badge.type'},
- tables=['award', 'badge'],
- order_by=['-awarded_at'],
- where=['user_id=%s AND badge_id=badge.id'],
- params=[user.id]
- ).values('id', 'name', 'description', 'type')
- total_awards = awards.count()
- awards = awards.annotate(count = Count('badge__id'))
- user_tags = user_tags.annotate(user_tag_usage_count=Count('name'))
-
- except ImportError:
- awards = Award.objects.extra(
- select={'id': 'badge.id',
- 'count': 'count(badge_id)',
- 'name':'badge.name',
- 'description': 'badge.description',
- 'type': 'badge.type'},
- tables=['award', 'badge'],
- order_by=['-awarded_at'],
- where=['user_id=%s AND badge_id=badge.id'],
- params=[user.id]
- ).values('id', 'count', 'name', 'description', 'type')
- total_awards = awards.count()
- awards.query.group_by = ['badge_id']
-
- user_tags = user_tags.extra(
- select={'user_tag_usage_count': 'COUNT(1)',},
- order_by=['-user_tag_usage_count'],
- )
- user_tags.query.group_by = ['name']
-
- if auth.can_moderate_users(request.user):
- moderate_user_form = ModerateUserForm(instance=user)
- else:
- moderate_user_form = None
-
- return render_to_response(user_view.template_file,{
- 'moderate_user_form': moderate_user_form,
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "questions" : questions,
- "answered_questions" : answered_questions,
- "up_votes" : up_votes,
- "down_votes" : down_votes,
- "total_votes": up_votes + down_votes,
- "votes_today_left": votes_total-votes_today,
- "votes_total_per_day": votes_total,
- "user_tags" : user_tags[:50],
- "tags" : tags,
- "awards": awards,
- "total_awards" : total_awards,
- }, context_instance=RequestContext(request))
-
-def user_recent(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- def get_type_name(type_id):
- for item in TYPE_ACTIVITY:
- if type_id in item:
- return item[1]
-
- class Event:
- def __init__(self, time, type, title, summary, answer_id, question_id):
- self.time = time
- self.type = get_type_name(type)
- self.type_id = type
- self.title = title
- self.summary = summary
- slug_title = slugify(title)
- self.title_link = reverse('question', kwargs={'id':question_id}) + u'%s' % slug_title
- if int(answer_id) > 0:
- self.title_link += '#%s' % answer_id
-
- class AwardEvent:
- def __init__(self, time, type, id):
- self.time = time
- self.type = get_type_name(type)
- self.type_id = type
- self.badge = get_object_or_404(Badge, id=id)
-
- activities = []
- # ask questions
- questions = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'active_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = ' +
- 'question.id AND question.deleted=False AND activity.user_id = %s AND activity.activity_type = %s'],
- params=[question_type_id, user_id, TYPE_ACTIVITY_ASK_QUESTION],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'active_at',
- 'activity_type'
- )
- if len(questions) > 0:
- questions = [(Event(q['active_at'], q['activity_type'], q['title'], '', '0', \
- q['question_id'])) for q in questions]
- activities.extend(questions)
-
- # answers
- answers = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'active_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'answer', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = answer.id AND ' +
- 'answer.question_id=question.id AND answer.deleted=False AND activity.user_id=%s AND '+
- 'activity.activity_type=%s AND question.deleted=False'],
- params=[answer_type_id, user_id, TYPE_ACTIVITY_ANSWER],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'active_at',
- 'activity_type'
- )
- if len(answers) > 0:
- answers = [(Event(q['active_at'], q['activity_type'], q['title'], '', q['answer_id'], \
- q['question_id'])) for q in answers]
- activities.extend(answers)
-
- # question comments
- comments = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'comment.object_id',
- 'added_at' : 'comment.added_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'question', 'comment'],
-
- where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+
- 'activity.user_id = comment.user_id AND comment.object_id=question.id AND '+
- 'comment.content_type_id=%s AND activity.user_id = %s AND activity.activity_type=%s AND ' +
- 'question.deleted=False'],
- params=[comment_type_id, question_type_id, user_id, TYPE_ACTIVITY_COMMENT_QUESTION],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'activity_type'
- )
-
- if len(comments) > 0:
- comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
- q['question_id'])) for q in comments]
- activities.extend(comments)
-
- # answer comments
- comments = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'comment.added_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'question', 'answer', 'comment'],
-
- where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+
- 'activity.user_id = comment.user_id AND comment.object_id=answer.id AND '+
- 'comment.content_type_id=%s AND question.id = answer.question_id AND '+
- 'activity.user_id = %s AND activity.activity_type=%s AND '+
- 'answer.deleted=False AND question.deleted=False'],
- params=[comment_type_id, answer_type_id, user_id, TYPE_ACTIVITY_COMMENT_ANSWER],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'activity_type'
- )
-
- if len(comments) > 0:
- comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', q['answer_id'], \
- q['question_id'])) for q in comments]
- activities.extend(comments)
-
- # question revisions
- revisions = Activity.objects.extra(
- select={
- 'title' : 'question_revision.title',
- 'question_id' : 'question_revision.question_id',
- 'added_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type',
- 'summary' : 'question_revision.summary'
- },
- tables=['activity', 'question_revision', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = question_revision.id AND '+
- 'question_revision.id=question.id AND question.deleted=False AND '+
- 'activity.user_id = question_revision.author_id AND activity.user_id = %s AND '+
- 'activity.activity_type=%s'],
- params=[question_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_QUESTION],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'activity_type',
- 'summary'
- )
-
- if len(revisions) > 0:
- revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], '0', \
- q['question_id'])) for q in revisions]
- activities.extend(revisions)
-
- # answer revisions
- revisions = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type',
- 'summary' : 'answer_revision.summary'
- },
- tables=['activity', 'answer_revision', 'question', 'answer'],
-
- where=['activity.content_type_id = %s AND activity.object_id = answer_revision.id AND '+
- 'activity.user_id = answer_revision.author_id AND activity.user_id = %s AND '+
- 'answer_revision.answer_id=answer.id AND answer.question_id = question.id AND '+
- 'question.deleted=False AND answer.deleted=False AND '+
- 'activity.activity_type=%s'],
- params=[answer_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_ANSWER],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'answer_id',
- 'activity_type',
- 'summary'
- )
-
- if len(revisions) > 0:
- revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], \
- q['answer_id'], q['question_id'])) for q in revisions]
- activities.extend(revisions)
-
- # accepted answers
- accept_answers = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'added_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type',
- },
- tables=['activity', 'answer', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = answer.id AND '+
- 'activity.user_id = question.author_id AND activity.user_id = %s AND '+
- 'answer.deleted=False AND question.deleted=False AND '+
- 'answer.question_id=question.id AND activity.activity_type=%s'],
- params=[answer_type_id, user_id, TYPE_ACTIVITY_MARK_ANSWER],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'activity_type',
- )
- if len(accept_answers) > 0:
- accept_answers = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
- q['question_id'])) for q in accept_answers]
- activities.extend(accept_answers)
- #award history
- awards = Activity.objects.extra(
- select={
- 'badge_id': 'badge.id',
- 'awarded_at': 'award.awarded_at',
- 'activity_type': 'activity.activity_type'
- },
- tables=['activity', 'award', 'badge'],
- where=['activity.user_id = award.user_id AND activity.user_id = %s AND ' +
- 'award.badge_id=badge.id AND activity.object_id=award.id AND activity.activity_type=%s'],
- params=[user_id, TYPE_ACTIVITY_PRIZE],
- order_by=['-activity.active_at']
- ).values(
- 'badge_id',
- 'awarded_at',
- 'activity_type'
- )
- if len(awards) > 0:
- awards = [(AwardEvent(q['awarded_at'], q['activity_type'], q['badge_id'])) for q in awards]
- activities.extend(awards)
-
- activities.sort(lambda x, y: cmp(y.time, x.time))
-
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "activities" : activities[:user_view.data_size]
- }, context_instance=RequestContext(request))
-
-def user_responses(request, user_id, user_view):
- """
- We list answers for question, comments, and answer accepted by others for this user.
- """
- class Response:
- def __init__(self, type, title, question_id, answer_id, time, username, user_id, content):
- self.type = type
- self.title = title
- self.titlelink = reverse('question', args=[question_id]) + u'%s#%s' % (slugify(title), answer_id)
- self.time = time
- self.userlink = reverse('users') + u'%s/%s/' % (user_id, username)
- self.username = username
- self.content = u'%s ...' % strip_tags(content)[:300]
-
- def __unicode__(self):
- return u'%s %s' % (self.type, self.titlelink)
-
- user = get_object_or_404(User, id=user_id)
- responses = []
- answers = Answer.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'answer.added_at',
- 'html' : 'answer.html',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- select_params=[user_id],
- tables=['answer', 'question', 'auth_user'],
- where=['answer.question_id = question.id AND answer.deleted=False AND question.deleted=False AND '+
- 'question.author_id = %s AND answer.author_id <> %s AND answer.author_id=auth_user.id'],
- params=[user_id, user_id],
- order_by=['-answer.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'html',
- 'username',
- 'user_id'
- )
-
- if len(answers) > 0:
- answers = [(Response(TYPE_RESPONSE['QUESTION_ANSWERED'], a['title'], a['question_id'],
- a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
- responses.extend(answers)
-
-
- # question comments
- comments = Comment.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'comment.object_id',
- 'added_at' : 'comment.added_at',
- 'comment' : 'comment.comment',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- tables=['question', 'auth_user', 'comment'],
- where=['question.deleted=False AND question.author_id = %s AND comment.object_id=question.id AND '+
- 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id'],
- params=[user_id, question_type_id, user_id],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'comment',
- 'username',
- 'user_id'
- )
-
- if len(comments) > 0:
- comments = [(Response(TYPE_RESPONSE['QUESTION_COMMENTED'], c['title'], c['question_id'],
- '', c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
- responses.extend(comments)
-
- # answer comments
- comments = Comment.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'comment.added_at',
- 'comment' : 'comment.comment',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- tables=['answer', 'auth_user', 'comment', 'question'],
- where=['answer.deleted=False AND answer.author_id = %s AND comment.object_id=answer.id AND '+
- 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id '+
- 'AND question.id = answer.question_id'],
- params=[user_id, answer_type_id, user_id],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'comment',
- 'username',
- 'user_id'
- )
-
- if len(comments) > 0:
- comments = [(Response(TYPE_RESPONSE['ANSWER_COMMENTED'], c['title'], c['question_id'],
- c['answer_id'], c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
- responses.extend(comments)
-
- # answer has been accepted
- answers = Answer.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'answer.accepted_at',
- 'html' : 'answer.html',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- select_params=[user_id],
- tables=['answer', 'question', 'auth_user'],
- where=['answer.question_id = question.id AND answer.deleted=False AND question.deleted=False AND '+
- 'answer.author_id = %s AND answer.accepted=True AND question.author_id=auth_user.id'],
- params=[user_id],
- order_by=['-answer.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'html',
- 'username',
- 'user_id'
- )
- if len(answers) > 0:
- answers = [(Response(TYPE_RESPONSE['ANSWER_ACCEPTED'], a['title'], a['question_id'],
- a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
- responses.extend(answers)
-
- # sort posts by time
- responses.sort(lambda x, y: cmp(y.time, x.time))
-
- return render_to_response(user_view.template_file, {
- "tab_name": user_view.id,
- "tab_description": user_view.tab_description,
- "page_title": user_view.page_title,
- "view_user": user,
- "responses": responses[:user_view.data_size],
-
- }, context_instance=RequestContext(request))
-
-def user_votes(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- if not can_view_user_votes(request.user, user):
- raise Http404
- votes = []
- question_votes = Vote.objects.extra(
- select={
- 'title': 'question.title',
- 'question_id': 'question.id',
- 'answer_id': 0,
- 'voted_at': 'vote.voted_at',
- 'vote': 'vote',
- },
- select_params=[user_id],
- tables=['vote', 'question', 'auth_user'],
- where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = question.id ' +
- 'AND vote.user_id=auth_user.id'],
- params=[question_type_id, user_id],
- order_by=['-vote.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'voted_at',
- 'vote',
- )
- if(len(question_votes) > 0):
- votes.extend(question_votes)
-
- answer_votes = Vote.objects.extra(
- select={
- 'title': 'question.title',
- 'question_id': 'question.id',
- 'answer_id': 'answer.id',
- 'voted_at': 'vote.voted_at',
- 'vote': 'vote',
- },
- select_params=[user_id],
- tables=['vote', 'answer', 'question', 'auth_user'],
- where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = answer.id ' +
- 'AND answer.question_id = question.id AND vote.user_id=auth_user.id'],
- params=[answer_type_id, user_id],
- order_by=['-vote.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'voted_at',
- 'vote',
- )
- if(len(answer_votes) > 0):
- votes.extend(answer_votes)
- votes.sort(lambda x, y: cmp(y['voted_at'], x['voted_at']))
- return render_to_response(user_view.template_file, {
- "tab_name": user_view.id,
- "tab_description": user_view.tab_description,
- "page_title": user_view.page_title,
- "view_user": user,
- "votes": votes[:user_view.data_size]
-
- }, context_instance=RequestContext(request))
-
-def user_reputation(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- try:
- from django.db.models import Sum
- reputation = Repute.objects.extra(
- select={'question_id':'question_id',
- 'title': 'question.title'},
- tables=['repute', 'question'],
- order_by=['-reputed_at'],
- where=['user_id=%s AND question_id=question.id'],
- params=[user.id]
- ).values('question_id', 'title', 'reputed_at', 'reputation')
- reputation = reputation.annotate(positive=Sum("positive"), negative=Sum("negative"))
- except ImportError:
- reputation = Repute.objects.extra(
- select={'positive':'sum(positive)', 'negative':'sum(negative)', 'question_id':'question_id',
- 'title': 'question.title'},
- tables=['repute', 'question'],
- order_by=['-reputed_at'],
- where=['user_id=%s AND question_id=question.id'],
- params=[user.id]
- ).values('positive', 'negative', 'question_id', 'title', 'reputed_at', 'reputation')
- reputation.query.group_by = ['question_id']
-
- rep_list = []
- for rep in Repute.objects.filter(user=user).order_by('reputed_at'):
- dic = '[%s,%s]' % (calendar.timegm(rep.reputed_at.timetuple()) * 1000, rep.reputation)
- rep_list.append(dic)
- reps = ','.join(rep_list)
- reps = '[%s]' % reps
-
- return render_to_response(user_view.template_file, {
- "tab_name": user_view.id,
- "tab_description": user_view.tab_description,
- "page_title": user_view.page_title,
- "view_user": user,
- "reputation": reputation,
- "reps": reps
- }, context_instance=RequestContext(request))
-
-def user_favorites(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- questions = Question.objects.extra(
- select={
- 'vote_count' : 'question.vote_up_count + question.vote_down_count',
- 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s '+
- 'AND f.question_id = question.id',
- 'la_user_id' : 'auth_user.id',
- 'la_username' : 'auth_user.username',
- 'la_user_gold' : 'auth_user.gold',
- 'la_user_silver' : 'auth_user.silver',
- 'la_user_bronze' : 'auth_user.bronze',
- 'la_user_reputation' : 'auth_user.reputation'
- },
- select_params=[user_id],
- tables=['question', 'auth_user', 'favorite_question'],
- where=['question.deleted=True AND question.last_activity_by_id = auth_user.id '+
- 'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'],
- params=[user_id],
- order_by=['-vote_count', '-question.id']
- ).values('vote_count',
- 'favorited_myself',
- 'id',
- 'title',
- 'author_id',
- 'added_at',
- 'answer_accepted',
- 'answer_count',
- 'comment_count',
- 'view_count',
- 'favourite_count',
- 'summary',
- 'tagnames',
- 'vote_up_count',
- 'vote_down_count',
- 'last_activity_at',
- 'la_user_id',
- 'la_username',
- 'la_user_gold',
- 'la_user_silver',
- 'la_user_bronze',
- 'la_user_reputation')
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "questions" : questions[:user_view.data_size],
- "view_user" : user
- }, context_instance=RequestContext(request))
-
-def user_email_subscriptions(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- if request.method == 'POST':
- email_feeds_form = EditUserEmailFeedsForm(request.POST)
- tag_filter_form = TagFilterSelectionForm(request.POST, instance=user)
- if email_feeds_form.is_valid() and tag_filter_form.is_valid():
-
- action_status = None
- tag_filter_saved = tag_filter_form.save()
- if tag_filter_saved:
- action_status = _('changes saved')
- if 'save' in request.POST:
- feeds_saved = email_feeds_form.save(user)
- if feeds_saved:
- action_status = _('changes saved')
- elif 'stop_email' in request.POST:
- email_stopped = email_feeds_form.reset().save(user)
- initial_values = EditUserEmailFeedsForm.NO_EMAIL_INITIAL
- email_feeds_form = EditUserEmailFeedsForm(initial=initial_values)
- if email_stopped:
- action_status = _('email updates canceled')
- else:
- email_feeds_form = EditUserEmailFeedsForm()
- email_feeds_form.set_initial_values(user)
- tag_filter_form = TagFilterSelectionForm(instance=user)
- action_status = None
- return render_to_response(user_view.template_file,{
- 'tab_name':user_view.id,
- 'tab_description':user_view.tab_description,
- 'page_title':user_view.page_title,
- 'view_user':user,
- 'email_feeds_form':email_feeds_form,
- 'tag_filter_selection_form':tag_filter_form,
- 'action_status':action_status,
- }, context_instance=RequestContext(request))
-
-def question_comments(request, id):
- question = get_object_or_404(Question, id=id)
- user = request.user
- return __comments(request, question, 'question')
-
-def answer_comments(request, id):
- answer = get_object_or_404(Answer, id=id)
- user = request.user
- return __comments(request, answer, 'answer')
-
-def __comments(request, obj, type):
- # only support get comments by ajax now
- user = request.user
- if request.is_ajax():
- if request.method == "GET":
- response = __generate_comments_json(obj, type, user)
- elif request.method == "POST":
- if auth.can_add_comments(user,obj):
- comment_data = request.POST.get('comment')
- comment = Comment(content_object=obj, comment=comment_data, user=request.user)
- comment.save()
- obj.comment_count = obj.comment_count + 1
- obj.save()
- response = __generate_comments_json(obj, type, user)
- else:
- response = HttpResponseForbidden(mimetype="application/json")
- return response
-
-def __generate_comments_json(obj, type, user):
- comments = obj.comments.all().order_by('id')
- # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
- json_comments = []
- from forum.templatetags.extra_tags import diff_date
- for comment in comments:
- comment_user = comment.user
- delete_url = ""
- if user != None and auth.can_delete_comment(user, comment):
- #/posts/392845/comments/219852/delete
- #todo translate this url
- delete_url = reverse(index) + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
- json_comments.append({"id" : comment.id,
- "object_id" : obj.id,
- "comment_age" : diff_date(comment.added_at),
- "text" : comment.comment,
- "user_display_name" : comment_user.username,
- "user_url" : comment_user.get_profile_url(),
- "delete_url" : delete_url
- })
-
- data = simplejson.dumps(json_comments)
- return HttpResponse(data, mimetype="application/json")
-
-def delete_comment(request, object_id='', comment_id='', commented_object_type=None):
- response = None
- commented_object = None
- if commented_object_type == 'question':
- commented_object = Question
- elif commented_object_type == 'answer':
- commented_object = Answer
-
- if request.is_ajax():
- comment = get_object_or_404(Comment, id=comment_id)
- if auth.can_delete_comment(request.user, comment):
- obj = get_object_or_404(commented_object, id=object_id)
- obj.comments.remove(comment)
- obj.comment_count = obj.comment_count - 1
- obj.save()
- user = request.user
- return __generate_comments_json(obj, commented_object_type, user)
- raise PermissionDenied()
-
-def logout(request):
- return render_to_response('logout.html', {
- 'next' : get_next_url(request),
- }, context_instance=RequestContext(request))
-
-def badges(request):
- badges = Badge.objects.all().order_by('type')
- my_badges = []
- if request.user.is_authenticated():
- my_badges = Award.objects.filter(user=request.user)
- my_badges.query.group_by = ['badge_id']
-
- return render_to_response('badges.html', {
- 'badges' : badges,
- 'mybadges' : my_badges,
- 'feedback_faq_url' : reverse('feedback'),
- }, context_instance=RequestContext(request))
-
-def badge(request, id):
- badge = get_object_or_404(Badge, id=id)
- awards = Award.objects.extra(
- select={'id': 'auth_user.id',
- 'name': 'auth_user.username',
- 'rep':'auth_user.reputation',
- 'gold': 'auth_user.gold',
- 'silver': 'auth_user.silver',
- 'bronze': 'auth_user.bronze'},
- tables=['award', 'auth_user'],
- where=['badge_id=%s AND user_id=auth_user.id'],
- params=[id]
- ).distinct('id')
-
- return render_to_response('badge.html', {
- 'awards': awards,
- 'badge': badge,
- }, context_instance=RequestContext(request))
-
-def read_message(request):
- if request.method == "POST":
- if request.POST['formdata'] == 'required':
- request.session['message_silent'] = 1
- if request.user.is_authenticated():
- request.user.delete_messages()
- return HttpResponse('')
-
-def upload(request):
- class FileTypeNotAllow(Exception):
- pass
- class FileSizeNotAllow(Exception):
- pass
- class UploadPermissionNotAuthorized(Exception):
- pass
-
- #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>
- xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>"
-
- try:
- f = request.FILES['file-upload']
- # check upload permission
- if not can_upload_files(request.user):
- raise UploadPermissionNotAuthorized
-
- # check file type
- file_name_suffix = os.path.splitext(f.name)[1].lower()
- if not file_name_suffix in settings.ALLOW_FILE_TYPES:
- raise FileTypeNotAllow
-
- # generate new file name
- new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix
- # use default storage to store file
- default_storage.save(new_file_name, f)
- # check file size
- # byte
- size = default_storage.size(new_file_name)
- if size > settings.ALLOW_MAX_FILE_SIZE:
- default_storage.delete(new_file_name)
- raise FileSizeNotAllow
-
- result = xml_template % ('Good', '', default_storage.url(new_file_name))
- except UploadPermissionNotAuthorized:
- result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '')
- except FileTypeNotAllow:
- result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '')
- except FileSizeNotAllow:
- result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
- except Exception:
- result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '')
-
- return HttpResponse(result, mimetype="application/xml")
-
-def books(request):
- return HttpResponseRedirect(reverse('books') + '/mysql-zhaoyang')
-
-def book(request, short_name, unanswered=False):
- """
- 1. questions list
- 2. book info
- 3. author info and blog rss items
- """
- """
- List of Questions, Tagged questions, and Unanswered questions.
- """
- books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
- match_count = len(books)
- if match_count == 0:
- raise Http404
- else:
- # the book info
- book = books[0]
- # get author info
- author_info = BookAuthorInfo.objects.get(book=book)
- # get author rss info
- author_rss = BookAuthorRss.objects.filter(book=book)
-
- # get pagesize from session, if failed then get default value
- user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
- # set pagesize equal to logon user specified value in database
- if request.user.is_authenticated() and request.user.questions_per_page > 0:
- user_page_size = request.user.questions_per_page
-
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score"}
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- # check if request is from tagged questions
- if unanswered:
- # check if request is from unanswered questions
- # Article.objects.filter(publications__id__exact=1)
- objects = Question.objects.filter(book__id__exact=book.id, deleted=False, answer_count=0).order_by(orderby)
- else:
- objects = Question.objects.filter(book__id__exact=book.id, deleted=False).order_by(orderby)
-
- # RISK - inner join queries
- objects = objects.select_related();
- objects_list = Paginator(objects, user_page_size)
- questions = objects_list.page(page)
-
- return render_to_response('book.html', {
- "book": book,
- "author_info": author_info,
- "author_rss": author_rss,
- "questions": questions,
- "context": {
- 'is_paginated': True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url': request.path + '?sort=%s&' % view_id,
- 'pagesize': user_page_size
- }
- }, context_instance=RequestContext(request))
-
-@login_required
-def ask_book(request, short_name):
- if request.method == "POST":
- form = AskForm(request.POST)
- if form.is_valid():
- added_at = datetime.datetime.now()
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- question = Question(
- title=strip_tags(form.cleaned_data['title']),
- author=request.user,
- added_at=added_at,
- last_activity_at=added_at,
- last_activity_by=request.user,
- wiki=form.cleaned_data['wiki'],
- tagnames=form.cleaned_data['tags'].strip(),
- html=html,
- summary=strip_tags(html)[:120]
- )
- if question.wiki:
- question.last_edited_by = question.author
- question.last_edited_at = added_at
- question.wikified_at = added_at
-
- question.save()
-
- # create the first revision
- QuestionRevision.objects.create(
- question=question,
- revision=1,
- title=question.title,
- author=request.user,
- revised_at=added_at,
- tagnames=question.tagnames,
- summary=CONST['default_version'],
- text=form.cleaned_data['text']
- )
-
- books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
- match_count = len(books)
- if match_count == 1:
- # the book info
- book = books[0]
- book.questions.add(question)
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = AskForm()
-
- tags = _get_tags_cache_json()
- return render_to_response('ask.html', {
- 'form' : form,
- 'tags' : tags,
- 'email_validation_faq_url': reverse('faq') + '#validate',
- }, context_instance=RequestContext(request))
-
-def search(request):
- """
- Search by question, user and tag keywords.
- For questions now we only search keywords in question title.
- """
- if request.method == "GET":
- keywords = request.GET.get("q")
- search_type = request.GET.get("t")
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
- if keywords is None:
- return HttpResponseRedirect(reverse(index))
- if search_type == 'tag':
- return HttpResponseRedirect(reverse('tags') + '?q=%s&page=%s' % (keywords.strip(), page))
- elif search_type == "user":
- return HttpResponseRedirect(reverse('users') + '?q=%s&page=%s' % (keywords.strip(), page))
- elif search_type == "question":
-
- template_file = "questions.html"
- # Set flag to False by default. If it is equal to True, then need to be saved.
- pagesize_changed = False
- # get pagesize from session, if failed then get default value
- user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
- # set pagesize equal to logon user specified value in database
- if request.user.is_authenticated() and request.user.questions_per_page > 0:
- user_page_size = request.user.questions_per_page
-
- try:
- page = int(request.GET.get('page', '1'))
- # get new pagesize from UI selection
- pagesize = int(request.GET.get('pagesize', user_page_size))
- if pagesize <> user_page_size:
- pagesize_changed = True
-
- except ValueError:
- page = 1
- pagesize = user_page_size
-
- # save this pagesize to user database
- if pagesize_changed:
- request.session["pagesize"] = pagesize
- if request.user.is_authenticated():
- user = request.user
- user.questions_per_page = pagesize
- user.save()
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score"}
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- if settings.USE_PG_FTS:
- objects = Question.objects.filter(deleted=False).extra(
- select={
- 'ranking': "ts_rank_cd(tsv, plainto_tsquery(%s), 32)",
- },
- where=["tsv @@ plainto_tsquery(%s)"],
- params=[keywords],
- select_params=[keywords]
- ).order_by('-ranking')
-
- elif settings.USE_SPHINX_SEARCH == True:
- #search index is now free of delete questions and answers
- #so there is not "antideleted" filtering here
- objects = Question.search.query(keywords)
- #no related selection either because we're relying on full text search here
- else:
- objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
- # RISK - inner join queries
- objects = objects.select_related();
-
- objects_list = Paginator(objects, pagesize)
- questions = objects_list.page(page)
-
- # Get related tags from this page objects
- related_tags = []
- for question in questions.object_list:
- tags = list(question.tags.all())
- for tag in tags:
- if tag not in related_tags:
- related_tags.append(tag)
-
- #if is_search is true in the context, prepend this string to soting tabs urls
- search_uri = "?q=%s&page=%d&t=question" % ("+".join(keywords.split()), page)
-
- return render_to_response(template_file, {
- "questions" : questions,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "searchtag" : None,
- "searchtitle" : keywords,
- "keywords" : keywords,
- "is_unanswered" : False,
- "is_search": True,
- "search_uri": search_uri,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
- 'pagesize' : pagesize
- }}, context_instance=RequestContext(request))
-
- else:
- raise Http404
diff --git a/forum/views/README b/forum/views/README
new file mode 100644
index 00000000..7b6201cd
--- /dev/null
+++ b/forum/views/README
@@ -0,0 +1,12 @@
+readers.py - views strictly reading main content: questions, answers, tags and comments
+
+writers.py - views that write main content, with possible reading
+ note: deletion counts as writing in this case
+
+commands.py - data status changing commands, votes, question close/reopen
+
+users.py - user views - user listing and profiles
+
+meta.py - privacy, about, faq, feedback, logout, badges
+
+books.py - book views - to be moved to a books extension
diff --git a/forum/views/__init__.py b/forum/views/__init__.py
index 7fdb6f61..291fee2a 100644
--- a/forum/views/__init__.py
+++ b/forum/views/__init__.py
@@ -1,4 +1,5 @@
-import content
+import readers
+import writers
+import commands
import users
import meta
-import books
diff --git a/forum/views/commands.py b/forum/views/commands.py
new file mode 100644
index 00000000..88c2c077
--- /dev/null
+++ b/forum/views/commands.py
@@ -0,0 +1,335 @@
+import datetime
+from django.conf import settings
+from django.utils import simplejson
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import get_object_or_404
+from django.utils.translation import ugettext as _
+from django.template import RequestContext
+from forum.models import *
+from forum.forms import CloseForm
+from forum import auth
+from django.core.urlresolvers import reverse
+from django.contrib.auth.decorators import login_required
+from forum.utils.decorators import ajax_method, ajax_login_required
+import logging
+
+def vote(request, id):#refactor - pretty incomprehensible view used by various ajax calls
+#issues: this subroutine is too long, contains many magic numbers and other issues
+#it's called "vote" but many actions processed here have nothing to do with voting
+ """
+ vote_type:
+ acceptAnswer : 0,
+ questionUpVote : 1,
+ questionDownVote : 2,
+ favorite : 4,
+ answerUpVote: 5,
+ answerDownVote:6,
+ offensiveQuestion : 7,
+ offensiveAnswer:8,
+ removeQuestion: 9,
+ removeAnswer:10
+ questionSubscribeUpdates:11
+ questionUnSubscribeUpdates:12
+
+ accept answer code:
+ response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default
+ response_data['success'] = 0, failed 1, Success - by default
+ response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel)
+
+ vote code:
+ allowed = -3, Don't have enough votes left
+ -2, Don't have enough reputation score
+ -1, Vote his own post
+ 0, no allowed - Anonymous
+ 1, Allowed - by default
+ status = 0, By default
+ 1, Cancel
+ 2, Vote is too old to be canceled
+
+ offensive code:
+ allowed = -3, Don't have enough flags left
+ -2, Don't have enough reputation score to do this
+ 0, not allowed
+ 1, allowed
+ status = 0, by default
+ 1, can't do it again
+ """
+ response_data = {
+ "allowed": 1,
+ "success": 1,
+ "status" : 0,
+ "count" : 0,
+ "message" : ''
+ }
+
+ def __can_vote(vote_score, user):#refactor - belongs to auth.py
+ if vote_score == 1:#refactor magic number
+ return auth.can_vote_up(request.user)
+ else:
+ return auth.can_vote_down(request.user)
+
+ try:
+ if not request.user.is_authenticated():
+ response_data['allowed'] = 0
+ response_data['success'] = 0
+
+ elif request.is_ajax() and request.method == 'POST':
+ question = get_object_or_404(Question, id=id)
+ vote_type = request.POST.get('type')
+
+ #accept answer
+ if vote_type == '0':
+ answer_id = request.POST.get('postId')
+ answer = get_object_or_404(Answer, id=answer_id)
+ # make sure question author is current user
+ if question.author == request.user:
+ # answer user who is also question author is not allow to accept answer
+ if answer.author == question.author:
+ response_data['success'] = 0
+ response_data['allowed'] = -1
+ # check if answer has been accepted already
+ elif answer.accepted:
+ auth.onAnswerAcceptCanceled(answer, request.user)
+ response_data['status'] = 1
+ else:
+ # set other answers in this question not accepted first
+ for answer_of_question in Answer.objects.get_answers_from_question(question, request.user):
+ if answer_of_question != answer and answer_of_question.accepted:
+ auth.onAnswerAcceptCanceled(answer_of_question, request.user)
+
+ #make sure retrieve data again after above author changes, they may have related data
+ answer = get_object_or_404(Answer, id=answer_id)
+ auth.onAnswerAccept(answer, request.user)
+ else:
+ response_data['allowed'] = 0
+ response_data['success'] = 0
+ # favorite
+ elif vote_type == '4':
+ has_favorited = False
+ fav_questions = FavoriteQuestion.objects.filter(question=question)
+ # if the same question has been favorited before, then delete it
+ if fav_questions is not None:
+ for item in fav_questions:
+ if item.user == request.user:
+ item.delete()
+ response_data['status'] = 1
+ response_data['count'] = len(fav_questions) - 1
+ if response_data['count'] < 0:
+ response_data['count'] = 0
+ has_favorited = True
+ # if above deletion has not been executed, just insert a new favorite question
+ if not has_favorited:
+ new_item = FavoriteQuestion(question=question, user=request.user)
+ new_item.save()
+ response_data['count'] = FavoriteQuestion.objects.filter(question=question).count()
+ Question.objects.update_favorite_count(question)
+
+ elif vote_type in ['1', '2', '5', '6']:
+ post_id = id
+ post = question
+ vote_score = 1
+ if vote_type in ['5', '6']:
+ answer_id = request.POST.get('postId')
+ answer = get_object_or_404(Answer, id=answer_id)
+ post_id = answer_id
+ post = answer
+ if vote_type in ['2', '6']:
+ vote_score = -1
+
+ if post.author == request.user:
+ response_data['allowed'] = -1
+ elif not __can_vote(vote_score, request.user):
+ response_data['allowed'] = -2
+ elif post.votes.filter(user=request.user).count() > 0:
+ vote = post.votes.filter(user=request.user)[0]
+ # unvote should be less than certain time
+ if (datetime.datetime.now().day - vote.voted_at.day) >= auth.VOTE_RULES['scope_deny_unvote_days']:
+ response_data['status'] = 2
+ else:
+ voted = vote.vote
+ if voted > 0:
+ # cancel upvote
+ auth.onUpVotedCanceled(vote, post, request.user)
+
+ else:
+ # cancel downvote
+ auth.onDownVotedCanceled(vote, post, request.user)
+
+ response_data['status'] = 1
+ response_data['count'] = post.score
+ elif Vote.objects.get_votes_count_today_from_user(request.user) >= auth.VOTE_RULES['scope_votes_per_user_per_day']:
+ response_data['allowed'] = -3
+ else:
+ vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
+ if vote_score > 0:
+ # upvote
+ auth.onUpVoted(vote, post, request.user)
+ else:
+ # downvote
+ auth.onDownVoted(vote, post, request.user)
+
+ votes_left = auth.VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user)
+ if votes_left <= auth.VOTE_RULES['scope_warn_votes_left']:
+ response_data['message'] = u'%s votes left' % votes_left
+ response_data['count'] = post.score
+ elif vote_type in ['7', '8']:
+ post = question
+ post_id = id
+ if vote_type == '8':
+ post_id = request.POST.get('postId')
+ post = get_object_or_404(Answer, id=post_id)
+
+ if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= auth.VOTE_RULES['scope_flags_per_user_per_day']:
+ response_data['allowed'] = -3
+ elif not auth.can_flag_offensive(request.user):
+ response_data['allowed'] = -2
+ elif post.flagged_items.filter(user=request.user).count() > 0:
+ response_data['status'] = 1
+ else:
+ item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now())
+ auth.onFlaggedItem(item, post, request.user)
+ response_data['count'] = post.offensive_flag_count
+ # send signal when question or answer be marked offensive
+ mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user)
+ elif vote_type in ['9', '10']:
+ post = question
+ post_id = id
+ if vote_type == '10':
+ post_id = request.POST.get('postId')
+ post = get_object_or_404(Answer, id=post_id)
+
+ if not auth.can_delete_post(request.user, post):
+ response_data['allowed'] = -2
+ elif post.deleted == True:
+ logging.debug('debug restoring post in view')
+ auth.onDeleteCanceled(post, request.user)
+ response_data['status'] = 1
+ else:
+ auth.onDeleted(post, request.user)
+ delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user)
+ elif vote_type == '11':#subscribe q updates
+ user = request.user
+ if user.is_authenticated():
+ if user not in question.followed_by.all():
+ question.followed_by.add(user)
+ if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False:
+ response_data['message'] = \
+ _('subscription saved, %(email)s needs validation, see %(details_url)s') \
+ % {'email':user.email,'details_url':reverse('faq') + '#validate'}
+ feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type='q_sel')
+ if feed_setting.frequency == 'n':
+ feed_setting.frequency = 'd'
+ feed_setting.save()
+ if 'message' in response_data:
+ response_data['message'] += '<br/>'
+ response_data['message'] = _('email update frequency has been set to daily')
+ #response_data['status'] = 1
+ #responst_data['allowed'] = 1
+ else:
+ pass
+ #response_data['status'] = 0
+ #response_data['allowed'] = 0
+ elif vote_type == '12':#unsubscribe q updates
+ user = request.user
+ if user.is_authenticated():
+ if user in question.followed_by.all():
+ question.followed_by.remove(user)
+ else:
+ response_data['success'] = 0
+ response_data['message'] = u'Request mode is not supported. Please try again.'
+
+ data = simplejson.dumps(response_data)
+
+ except Exception, e:
+ response_data['message'] = str(e)
+ data = simplejson.dumps(response_data)
+ return HttpResponse(data, mimetype="application/json")
+
+#internally grouped views - used by the tagging system
+@ajax_login_required
+def mark_tag(request, tag=None, **kwargs):#tagging system
+ action = kwargs['action']
+ ts = MarkedTag.objects.filter(user=request.user, tag__name=tag)
+ if action == 'remove':
+ logging.debug('deleting tag %s' % tag)
+ ts.delete()
+ else:
+ reason = kwargs['reason']
+ if len(ts) == 0:
+ try:
+ t = Tag.objects.get(name=tag)
+ mt = MarkedTag(user=request.user, reason=reason, tag=t)
+ mt.save()
+ except:
+ pass
+ else:
+ ts.update(reason=reason)
+ return HttpResponse(simplejson.dumps(''), mimetype="application/json")
+
+@ajax_login_required
+def ajax_toggle_ignored_questions(request):#ajax tagging and tag-filtering system
+ if request.user.hide_ignored_questions:
+ new_hide_setting = False
+ else:
+ new_hide_setting = True
+ request.user.hide_ignored_questions = new_hide_setting
+ request.user.save()
+
+@ajax_method
+def ajax_command(request):#refactor? view processing ajax commands - note "vote" and view others do it too
+ if 'command' not in request.POST:
+ return HttpResponseForbidden(mimetype="application/json")
+ if request.POST['command'] == 'toggle-ignored-questions':
+ return ajax_toggle_ignored_questions(request)
+
+@login_required
+def close(request, id):#close question
+ """view to initiate and process
+ question close
+ """
+ question = get_object_or_404(Question, id=id)
+ if not auth.can_close_question(request.user, question):
+ return HttpResponse('Permission denied.')
+ if request.method == 'POST':
+ form = CloseForm(request.POST)
+ if form.is_valid():
+ reason = form.cleaned_data['reason']
+ question.closed = True
+ question.closed_by = request.user
+ question.closed_at = datetime.datetime.now()
+ question.close_reason = reason
+ question.save()
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = CloseForm()
+ return render_to_response('close.html', {
+ 'form' : form,
+ 'question' : question,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def reopen(request, id):#re-open question
+ """view to initiate and process
+ question close
+ """
+ question = get_object_or_404(Question, id=id)
+ # open question
+ if not auth.can_reopen_question(request.user, question):
+ return HttpResponse('Permission denied.')
+ if request.method == 'POST' :
+ Question.objects.filter(id=question.id).update(closed=False,
+ closed_by=None, closed_at=None, close_reason=None)
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ return render_to_response('reopen.html', {
+ 'question' : question,
+ }, context_instance=RequestContext(request))
+
+#osqa-user communication system
+def read_message(request):#marks message a read
+ if request.method == "POST":
+ if request.POST['formdata'] == 'required':
+ request.session['message_silent'] = 1
+ if request.user.is_authenticated():
+ request.user.delete_messages()
+ return HttpResponse('')
diff --git a/forum/views/content.py b/forum/views/content.py
deleted file mode 100644
index 3fd3017d..00000000
--- a/forum/views/content.py
+++ /dev/null
@@ -1,1394 +0,0 @@
-# encoding:utf-8
-import os.path
-import time, datetime, calendar, random
-import logging
-from urllib import quote, unquote
-from django.conf import settings
-from django.core.files.storage import default_storage
-from django.shortcuts import render_to_response, get_object_or_404
-from django.contrib.auth.decorators import login_required
-from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
-from django.core.paginator import Paginator, EmptyPage, InvalidPage
-from django.template import RequestContext, loader
-from django.utils.html import *
-from django.utils import simplejson
-from django.core import serializers
-from django.db import transaction
-from django.db.models import Count, Q
-from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import ugettext as _
-from django.utils.datastructures import SortedDict
-from django.template.defaultfilters import slugify
-from django.core.exceptions import PermissionDenied
-
-from utils.html import sanitize_html
-from utils.decorators import ajax_method, ajax_login_required
-from markdown2 import Markdown
-#from lxml.html.diff import htmldiff
-from forum.diff import textDiff as htmldiff
-from forum.forms import *
-from forum.models import *
-from forum.auth import *
-from forum.const import *
-from forum import auth
-from utils.forms import get_next_url
-
-# used in index page
-INDEX_PAGE_SIZE = 20
-INDEX_AWARD_SIZE = 15
-INDEX_TAGS_SIZE = 100
-# used in tags list
-DEFAULT_PAGE_SIZE = 60
-# used in questions
-QUESTIONS_PAGE_SIZE = 10
-# used in answers
-ANSWERS_PAGE_SIZE = 10
-
-markdowner = Markdown(html4tags=True)
-
-#system to display main content
-def _get_tags_cache_json():#service routine used by views requiring tag list in the javascript space
- """returns list of all tags in json format
- no caching yet, actually
- """
- tags = Tag.objects.filter(deleted=False).all()
- tags_list = []
- for tag in tags:
- dic = {'n': tag.name, 'c': tag.used_count}
- tags_list.append(dic)
- tags = simplejson.dumps(tags_list)
- return tags
-
-def _get_and_remember_questions_sort_method(request, view_dic, default):#service routine used by q listing views and question view
- """manages persistence of post sort order
- it is assumed that when user wants newest question -
- then he/she wants newest answers as well, etc.
- how far should this assumption actually go - may be a good question
- """
- if default not in view_dic:
- raise Exception('default value must be in view_dic')
-
- q_sort_method = request.REQUEST.get('sort', None)
- if q_sort_method == None:
- q_sort_method = request.session.get('questions_sort_method', default)
-
- if q_sort_method not in view_dic:
- q_sort_method = default
- request.session['questions_sort_method'] = q_sort_method
- return q_sort_method, view_dic[q_sort_method]
-
-#refactor? - we have these
-#views that generate a listing of questions in one way or another:
-#index, unanswered, questions, search, tag
-#should we dry them up?
-#related topics - information drill-down, search refinement
-
-def index(request):#generates front page - shows listing of questions sorted in various ways
- """index view mapped to the root url of the Q&A site
- """
- view_dic = {
- "latest":"-last_activity_at",
- "hottest":"-answer_count",
- "mostvoted":"-score",
- }
- view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest')
-
- page_size = request.session.get('pagesize', QUESTIONS_PAGE_SIZE)
- questions = Question.objects.exclude(deleted=True).order_by(orderby)[:page_size]
- # RISK - inner join queries
- questions = questions.select_related()
- tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE)
-
- awards = Award.objects.get_recent_awards()
-
- (interesting_tag_names, ignored_tag_names) = (None, None)
- if request.user.is_authenticated():
- pt = MarkedTag.objects.filter(user=request.user)
- interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
- ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
-
- tags_autocomplete = _get_tags_cache_json()
-
- return render_to_response('index.html', {
- 'interesting_tag_names': interesting_tag_names,
- 'tags_autocomplete': tags_autocomplete,
- 'ignored_tag_names': ignored_tag_names,
- "questions" : questions,
- "tab_id" : view_id,
- "tags" : tags,
- "awards" : awards[:INDEX_AWARD_SIZE],
- }, context_instance=RequestContext(request))
-
-def unanswered(request):#generates listing of unanswered questions
- return questions(request, unanswered=True)
-
-def questions(request, tagname=None, unanswered=False):#a view generating listing of questions, used by 'unanswered' too
- """
- List of Questions, Tagged questions, and Unanswered questions.
- """
- # template file
- # "questions.html" or maybe index.html in the future
- template_file = "questions.html"
- # Set flag to False by default. If it is equal to True, then need to be saved.
- pagesize_changed = False
- # get pagesize from session, if failed then get default value
- pagesize = request.session.get("pagesize",10)
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- view_id, orderby = _get_and_remember_questions_sort_method(request,view_dic,'latest')
-
- # check if request is from tagged questions
- qs = Question.objects.exclude(deleted=True)
-
- if tagname is not None:
- qs = qs.filter(tags__name = unquote(tagname))
-
- if unanswered:
- qs = qs.exclude(answer_accepted=True)
-
- author_name = None
- #user contributed questions & answers
- if 'user' in request.GET:
- try:
- author_name = request.GET['user']
- u = User.objects.get(username=author_name)
- qs = qs.filter(Q(author=u) | Q(answers__author=u))
- except User.DoesNotExist:
- author_name = None
-
- if request.user.is_authenticated():
- uid_str = str(request.user.id)
- qs = qs.extra(
- select = SortedDict([
- (
- 'interesting_score',
- 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
- + 'WHERE forum_markedtag.user_id = %s '
- + 'AND forum_markedtag.tag_id = question_tags.tag_id '
- + 'AND forum_markedtag.reason = \'good\' '
- + 'AND question_tags.question_id = question.id'
- ),
- ]),
- select_params = (uid_str,),
- )
- if request.user.hide_ignored_questions:
- ignored_tags = Tag.objects.filter(user_selections__reason='bad',
- user_selections__user = request.user)
- qs = qs.exclude(tags__in=ignored_tags)
- else:
- qs = qs.extra(
- select = SortedDict([
- (
- 'ignored_score',
- 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
- + 'WHERE forum_markedtag.user_id = %s '
- + 'AND forum_markedtag.tag_id = question_tags.tag_id '
- + 'AND forum_markedtag.reason = \'bad\' '
- + 'AND question_tags.question_id = question.id'
- )
- ]),
- select_params = (uid_str, )
- )
-
- qs = qs.select_related(depth=1).order_by(orderby)
-
- objects_list = Paginator(qs, pagesize)
- questions = objects_list.page(page)
-
- # Get related tags from this page objects
- if questions.object_list.count() > 0:
- related_tags = Tag.objects.get_tags_by_questions(questions.object_list)
- else:
- related_tags = None
- tags_autocomplete = _get_tags_cache_json()
-
- # get the list of interesting and ignored tags
- (interesting_tag_names, ignored_tag_names) = (None, None)
- if request.user.is_authenticated():
- pt = MarkedTag.objects.filter(user=request.user)
- interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
- ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
-
- return render_to_response(template_file, {
- "questions" : questions,
- "author_name" : author_name,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "tags_autocomplete" : tags_autocomplete,
- "searchtag" : tagname,
- "is_unanswered" : unanswered,
- "interesting_tag_names": interesting_tag_names,
- 'ignored_tag_names': ignored_tag_names,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'pagesize' : pagesize
- }}, context_instance=RequestContext(request))
-
-def search(request): #generates listing of questions matching a search query - including tags and just words
- """generates listing of questions matching a search query
- supports full text search in mysql db using sphinx and internally in postgresql
- falls back on simple partial string matching approach if
- full text search function is not available
- """
- if request.method == "GET":
- keywords = request.GET.get("q")
- search_type = request.GET.get("t")
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
- if keywords is None:
- return HttpResponseRedirect(reverse(index))
- if search_type == 'tag':
- return HttpResponseRedirect(reverse('tags') + '?q=%s&page=%s' % (keywords.strip(), page))
- elif search_type == "user":
- return HttpResponseRedirect(reverse('users') + '?q=%s&page=%s' % (keywords.strip(), page))
- elif search_type == "question":
-
- template_file = "questions.html"
- # Set flag to False by default. If it is equal to True, then need to be saved.
- pagesize_changed = False
- # get pagesize from session, if failed then get default value
- user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
- # set pagesize equal to logon user specified value in database
- if request.user.is_authenticated() and request.user.questions_per_page > 0:
- user_page_size = request.user.questions_per_page
-
- try:
- page = int(request.GET.get('page', '1'))
- # get new pagesize from UI selection
- pagesize = int(request.GET.get('pagesize', user_page_size))
- if pagesize <> user_page_size:
- pagesize_changed = True
-
- except ValueError:
- page = 1
- pagesize = user_page_size
-
- # save this pagesize to user database
- if pagesize_changed:
- request.session["pagesize"] = pagesize
- if request.user.is_authenticated():
- user = request.user
- user.questions_per_page = pagesize
- user.save()
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- if settings.USE_PG_FTS:
- objects = Question.objects.filter(deleted=False).extra(
- select={
- 'ranking': "ts_rank_cd(tsv, plainto_tsquery(%s), 32)",
- },
- where=["tsv @@ plainto_tsquery(%s)"],
- params=[keywords],
- select_params=[keywords]
- ).order_by('-ranking')
-
- elif settings.USE_SPHINX_SEARCH == True:
- #search index is now free of delete questions and answers
- #so there is not "antideleted" filtering here
- objects = Question.search.query(keywords)
- #no related selection either because we're relying on full text search here
- else:
- objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
- # RISK - inner join queries
- objects = objects.select_related();
-
- objects_list = Paginator(objects, pagesize)
- questions = objects_list.page(page)
-
- # Get related tags from this page objects
- related_tags = []
- for question in questions.object_list:
- tags = list(question.tags.all())
- for tag in tags:
- if tag not in related_tags:
- related_tags.append(tag)
-
- #if is_search is true in the context, prepend this string to soting tabs urls
- search_uri = "?q=%s&page=%d&t=question" % ("+".join(keywords.split()), page)
-
- return render_to_response(template_file, {
- "questions" : questions,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "searchtag" : None,
- "searchtitle" : keywords,
- "keywords" : keywords,
- "is_unanswered" : False,
- "is_search": True,
- "search_uri": search_uri,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
- 'pagesize' : pagesize
- }}, context_instance=RequestContext(request))
-
- else:
- raise Http404
-
-def tag(request, tag):#stub generates listing of questions tagged with a single tag
- return questions(request, tagname=tag)
-
-def tags(request):#view showing a listing of available tags - plain list
- stag = ""
- is_paginated = True
- sortby = request.GET.get('sort', 'used')
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- if request.method == "GET":
- stag = request.GET.get("q", "").strip()
- if stag != '':
- objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE)
- else:
- if sortby == "name":
- objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE)
- else:
- objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE)
-
- try:
- tags = objects_list.page(page)
- except (EmptyPage, InvalidPage):
- tags = objects_list.page(objects_list.num_pages)
-
- return render_to_response('tags.html', {
- "tags" : tags,
- "stag" : stag,
- "tab_id" : sortby,
- "keywords" : stag,
- "context" : {
- 'is_paginated' : is_paginated,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': tags.has_previous(),
- 'has_next': tags.has_next(),
- 'previous': tags.previous_page_number(),
- 'next': tags.next_page_number(),
- 'base_url' : reverse('tags') + '?sort=%s&' % sortby
- }
- }, context_instance=RequestContext(request))
-
-def question(request, id):#refactor - long subroutine. display question body, answers and comments
- """view that displays body of the question and
- all answers to it
- """
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- qsm = request.session.get('questions_sort_method',None)
- if qsm in ('mostvoted','latest'):
- logging.debug('loaded from session ' + qsm)
- if qsm == 'mostvoted':
- view_id = 'votes'
- orderby = '-score'
- else:
- view_id = 'latest'
- orderby = '-added_at'
- else:
- view_id = "votes"
- orderby = "-score"
-
- logging.debug('view_id=' + str(view_id))
-
- question = get_object_or_404(Question, id=id)
- try:
- pattern = r'/%s%s%d/([\w-]+)' % (settings.FORUM_SCRIPT_ALIAS,_('question/'), question.id)
- path_re = re.compile(pattern)
- logging.debug(pattern)
- logging.debug(request.path)
- m = path_re.match(request.path)
- if m:
- slug = m.group(1)
- logging.debug('have slug %s' % slug)
- assert(slug == slugify(question.title))
- else:
- logging.debug('no match!')
- except:
- return HttpResponseRedirect(question.get_absolute_url())
-
- if question.deleted and not auth.can_view_deleted_post(request.user, question):
- raise Http404
- answer_form = AnswerForm(question,request.user)
- answers = Answer.objects.get_answers_from_question(question, request.user)
- answers = answers.select_related(depth=1)
-
- favorited = question.has_favorite_by_user(request.user)
- if request.user.is_authenticated():
- question_vote = question.votes.select_related().filter(user=request.user)
- else:
- question_vote = None #is this correct?
- if question_vote is not None and question_vote.count() > 0:
- question_vote = question_vote[0]
-
- user_answer_votes = {}
- for answer in answers:
- vote = answer.get_user_vote(request.user)
- if vote is not None and not user_answer_votes.has_key(answer.id):
- vote_value = -1
- if vote.is_upvote():
- vote_value = 1
- user_answer_votes[answer.id] = vote_value
-
- if answers is not None:
- answers = answers.order_by("-accepted", orderby)
-
- filtered_answers = []
- for answer in answers:
- if answer.deleted == True:
- if answer.author_id == request.user.id:
- filtered_answers.append(answer)
- else:
- filtered_answers.append(answer)
-
- objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE)
- page_objects = objects_list.page(page)
-
- #todo: merge view counts per user and per session
- #1) view count per session
- update_view_count = False
- if 'question_view_times' not in request.session:
- request.session['question_view_times'] = {}
-
- last_seen = request.session['question_view_times'].get(question.id,None)
- updated_when, updated_who = question.get_last_update_info()
-
- if updated_who != request.user:
- if last_seen:
- if last_seen < updated_when:
- update_view_count = True
- else:
- update_view_count = True
-
- request.session['question_view_times'][question.id] = datetime.datetime.now()
-
- if update_view_count:
- question.view_count += 1
- question.save()
-
- #2) question view count per user
- if request.user.is_authenticated():
- try:
- question_view = QuestionView.objects.get(who=request.user, question=question)
- except QuestionView.DoesNotExist:
- question_view = QuestionView(who=request.user, question=question)
- question_view.when = datetime.datetime.now()
- question_view.save()
-
- return render_to_response('question.html', {
- "question" : question,
- "question_vote" : question_vote,
- "question_comment_count":question.comments.count(),
- "answer" : answer_form,
- "answers" : page_objects.object_list,
- "user_answer_votes": user_answer_votes,
- "tags" : question.tags.all(),
- "tab_id" : view_id,
- "favorited" : favorited,
- "similar_questions" : Question.objects.get_similar_questions(question),
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': page_objects.has_previous(),
- 'has_next': page_objects.has_next(),
- 'previous': page_objects.previous_page_number(),
- 'next': page_objects.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'extend_url' : "#sort-top"
- }
- }, context_instance=RequestContext(request))
-
-QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n'
- '<div class="text">%(html)s</div>\n'
- '<div class="tags">%(tags)s</div>')
-def question_revisions(request, id):
- post = get_object_or_404(Question, id=id)
- revisions = list(post.revisions.all())
- revisions.reverse()
- for i, revision in enumerate(revisions):
- revision.html = QUESTION_REVISION_TEMPLATE % {
- 'title': revision.title,
- 'html': sanitize_html(markdowner.convert(revision.text)),
- 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
- for tag in revision.tagnames.split(' ')]),
- }
- if i > 0:
- revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
- else:
- revisions[i].diff = QUESTION_REVISION_TEMPLATE % {
- 'title': revisions[0].title,
- 'html': sanitize_html(markdowner.convert(revisions[0].text)),
- 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
- for tag in revisions[0].tagnames.split(' ')]),
- }
- revisions[i].summary = _('initial version')
- return render_to_response('revisions_question.html', {
- 'post': post,
- 'revisions': revisions,
- }, context_instance=RequestContext(request))
-
-ANSWER_REVISION_TEMPLATE = ('<div class="text">%(html)s</div>')
-def answer_revisions(request, id):
- post = get_object_or_404(Answer, id=id)
- revisions = list(post.revisions.all())
- revisions.reverse()
- for i, revision in enumerate(revisions):
- revision.html = ANSWER_REVISION_TEMPLATE % {
- 'html': sanitize_html(markdowner.convert(revision.text))
- }
- if i > 0:
- revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
- else:
- revisions[i].diff = revisions[i].text
- revisions[i].summary = _('initial version')
- return render_to_response('revisions_answer.html', {
- 'post': post,
- 'revisions': revisions,
- }, context_instance=RequestContext(request))
-#system to collect user actions and change content and store in the database
-def create_new_answer( question=None, author=None, #service subroutine - refactor
- added_at=None, wiki=False,\
- text='', email_notify=False):
- """refactor
- non-view subroutine
- initializes the answer and revision
- and updates stuff in the corresponding question
- probably there is more Django-ish way to do it
- """
-
- html = sanitize_html(markdowner.convert(text))
-
- #create answer
- answer = Answer(
- question = question,
- author = author,
- added_at = added_at,
- wiki = wiki,
- html = html
- )
- if answer.wiki:
- answer.last_edited_by = answer.author
- answer.last_edited_at = added_at
- answer.wikified_at = added_at
-
- answer.save()
-
- #update question data
- question.last_activity_at = added_at
- question.last_activity_by = author
- question.save()
- Question.objects.update_answer_count(question)
-
- #update revision
- AnswerRevision.objects.create(
- answer = answer,
- revision = 1,
- author = author,
- revised_at = added_at,
- summary = CONST['default_version'],
- text = text
- )
-
- #set notification/delete
- if email_notify:
- if author not in question.followed_by.all():
- question.followed_by.add(author)
- else:
- #not sure if this is necessary. ajax should take care of this...
- try:
- question.followed_by.remove(author)
- except:
- pass
-
-def create_new_question(title=None,author=None,added_at=None, #service subroutine - refactor
- wiki=False,tagnames=None,summary=None,
- text=None):
- """refactor
- this is not a view saves new question and revision
- and maybe should become one of the methods on Question object?
- """
- html = sanitize_html(markdowner.convert(text))
- question = Question(
- title = title,
- author = author,
- added_at = added_at,
- last_activity_at = added_at,
- last_activity_by = author,
- wiki = wiki,
- tagnames = tagnames,
- html = html,
- summary = summary
- )
- if question.wiki:
- question.last_edited_by = question.author
- question.last_edited_at = added_at
- question.wikified_at = added_at
-
- question.save()
-
- # create the first revision
- QuestionRevision.objects.create(
- question = question,
- revision = 1,
- title = question.title,
- author = author,
- revised_at = added_at,
- tagnames = question.tagnames,
- summary = CONST['default_version'],
- text = text
- )
- return question
-
-def upload(request):#ajax upload file to a question or answer
- class FileTypeNotAllow(Exception):
- pass
- class FileSizeNotAllow(Exception):
- pass
- class UploadPermissionNotAuthorized(Exception):
- pass
-
- #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>
- xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>"
-
- try:
- f = request.FILES['file-upload']
- # check upload permission
- if not auth.can_upload_files(request.user):
- raise UploadPermissionNotAuthorized
-
- # check file type
- file_name_suffix = os.path.splitext(f.name)[1].lower()
- if not file_name_suffix in settings.ALLOW_FILE_TYPES:
- raise FileTypeNotAllow
-
- # generate new file name
- new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix
- # use default storage to store file
- default_storage.save(new_file_name, f)
- # check file size
- # byte
- size = default_storage.size(new_file_name)
- if size > settings.ALLOW_MAX_FILE_SIZE:
- default_storage.delete(new_file_name)
- raise FileSizeNotAllow
-
- result = xml_template % ('Good', '', default_storage.url(new_file_name))
- except UploadPermissionNotAuthorized:
- result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '')
- except FileTypeNotAllow:
- result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '')
- except FileSizeNotAllow:
- result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
- except Exception:
- result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '')
-
- return HttpResponse(result, mimetype="application/xml")
-
-#@login_required #actually you can post anonymously, but then must register
-def ask(request):#view used to ask a new question
- """a view to ask a new question
- gives space for q title, body, tags and checkbox for to post as wiki
-
- user can start posting a question anonymously but then
- must login/register in order for the question go be shown
- """
- if request.method == "POST":
- form = AskForm(request.POST)
- if form.is_valid():
-
- added_at = datetime.datetime.now()
- title = strip_tags(form.cleaned_data['title'].strip())
- wiki = form.cleaned_data['wiki']
- tagnames = form.cleaned_data['tags'].strip()
- text = form.cleaned_data['text']
- html = sanitize_html(markdowner.convert(text))
- summary = strip_tags(html)[:120]
-
- if request.user.is_authenticated():
- author = request.user
-
- question = create_new_question(
- title = title,
- author = author,
- added_at = added_at,
- wiki = wiki,
- tagnames = tagnames,
- summary = summary,
- text = text
- )
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- request.session.flush()
- session_key = request.session.session_key
- question = AnonymousQuestion(
- session_key = session_key,
- title = title,
- tagnames = tagnames,
- wiki = wiki,
- text = text,
- summary = summary,
- added_at = added_at,
- ip_addr = request.META['REMOTE_ADDR'],
- )
- question.save()
- return HttpResponseRedirect(reverse('user_signin_new_question'))
- else:
- form = AskForm()
-
- tags = _get_tags_cache_json()
- return render_to_response('ask.html', {
- 'form' : form,
- 'tags' : tags,
- 'email_validation_faq_url':reverse('faq') + '#validate',
- }, context_instance=RequestContext(request))
-
-@login_required
-def close(request, id):#close question
- """view to initiate and process
- question close
- """
- question = get_object_or_404(Question, id=id)
- if not auth.can_close_question(request.user, question):
- return HttpResponse('Permission denied.')
- if request.method == 'POST':
- form = CloseForm(request.POST)
- if form.is_valid():
- reason = form.cleaned_data['reason']
- question.closed = True
- question.closed_by = request.user
- question.closed_at = datetime.datetime.now()
- question.close_reason = reason
- question.save()
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = CloseForm()
- return render_to_response('close.html', {
- 'form' : form,
- 'question' : question,
- }, context_instance=RequestContext(request))
-
-@login_required
-def reopen(request, id):#re-open question
- """view to initiate and process
- question close
- """
- question = get_object_or_404(Question, id=id)
- # open question
- if not auth.can_reopen_question(request.user, question):
- return HttpResponse('Permission denied.')
- if request.method == 'POST' :
- Question.objects.filter(id=question.id).update(closed=False,
- closed_by=None, closed_at=None, close_reason=None)
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- return render_to_response('reopen.html', {
- 'question' : question,
- }, context_instance=RequestContext(request))
-
-@login_required
-def edit_question(request, id):#edit or retag a question
- """view to edit question
- """
- question = get_object_or_404(Question, id=id)
- if question.deleted and not auth.can_view_deleted_post(request.user, question):
- raise Http404
- if auth.can_edit_post(request.user, question):
- return _edit_question(request, question)
- elif auth.can_retag_questions(request.user):
- return _retag_question(request, question)
- else:
- raise Http404
-
-def _retag_question(request, question):#non-url subview of edit question - just retag
- """retag question sub-view used by
- view "edit_question"
- """
- if request.method == 'POST':
- form = RetagQuestionForm(question, request.POST)
- if form.is_valid():
- if form.has_changed():
- latest_revision = question.get_latest_revision()
- retagged_at = datetime.datetime.now()
- # Update the Question itself
- Question.objects.filter(id=question.id).update(
- tagnames = form.cleaned_data['tags'],
- last_edited_at = retagged_at,
- last_edited_by = request.user,
- last_activity_at = retagged_at,
- last_activity_by = request.user
- )
- # Update the Question's tag associations
- tags_updated = Question.objects.update_tags(question,
- form.cleaned_data['tags'], request.user)
- # Create a new revision
- QuestionRevision.objects.create(
- question = question,
- title = latest_revision.title,
- author = request.user,
- revised_at = retagged_at,
- tagnames = form.cleaned_data['tags'],
- summary = CONST['retagged'],
- text = latest_revision.text
- )
- # send tags updated singal
- tags_updated.send(sender=question.__class__, question=question)
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = RetagQuestionForm(question)
- return render_to_response('question_retag.html', {
- 'question': question,
- 'form' : form,
- 'tags' : _get_tags_cache_json(),
- }, context_instance=RequestContext(request))
-
-def _edit_question(request, question):#non-url subview of edit_question - just edit the body/title
- latest_revision = question.get_latest_revision()
- revision_form = None
- if request.method == 'POST':
- if 'select_revision' in request.POST:
- # user has changed revistion number
- revision_form = RevisionForm(question, latest_revision, request.POST)
- if revision_form.is_valid():
- # Replace with those from the selected revision
- form = EditQuestionForm(question,
- QuestionRevision.objects.get(question=question,
- revision=revision_form.cleaned_data['revision']))
- else:
- form = EditQuestionForm(question, latest_revision, request.POST)
- else:
- # Always check modifications against the latest revision
- form = EditQuestionForm(question, latest_revision, request.POST)
- if form.is_valid():
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- if form.has_changed():
- edited_at = datetime.datetime.now()
- tags_changed = (latest_revision.tagnames !=
- form.cleaned_data['tags'])
- tags_updated = False
- # Update the Question itself
- updated_fields = {
- 'title': form.cleaned_data['title'],
- 'last_edited_at': edited_at,
- 'last_edited_by': request.user,
- 'last_activity_at': edited_at,
- 'last_activity_by': request.user,
- 'tagnames': form.cleaned_data['tags'],
- 'summary': strip_tags(html)[:120],
- 'html': html,
- }
-
- # only save when it's checked
- # because wiki doesn't allow to be edited if last version has been enabled already
- # and we make sure this in forms.
- if ('wiki' in form.cleaned_data and
- form.cleaned_data['wiki']):
- updated_fields['wiki'] = True
- updated_fields['wikified_at'] = edited_at
-
- Question.objects.filter(
- id=question.id).update(**updated_fields)
- # Update the Question's tag associations
- if tags_changed:
- tags_updated = Question.objects.update_tags(
- question, form.cleaned_data['tags'], request.user)
- # Create a new revision
- revision = QuestionRevision(
- question = question,
- title = form.cleaned_data['title'],
- author = request.user,
- revised_at = edited_at,
- tagnames = form.cleaned_data['tags'],
- text = form.cleaned_data['text'],
- )
- if form.cleaned_data['summary']:
- revision.summary = form.cleaned_data['summary']
- else:
- revision.summary = 'No.%s Revision' % latest_revision.revision
- revision.save()
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
-
- revision_form = RevisionForm(question, latest_revision)
- form = EditQuestionForm(question, latest_revision)
- return render_to_response('question_edit.html', {
- 'question': question,
- 'revision_form': revision_form,
- 'form' : form,
- 'tags' : _get_tags_cache_json()
- }, context_instance=RequestContext(request))
-
-@login_required
-def edit_answer(request, id):
- answer = get_object_or_404(Answer, id=id)
- if answer.deleted and not auth.can_view_deleted_post(request.user, answer):
- raise Http404
- elif not auth.can_edit_post(request.user, answer):
- raise Http404
- else:
- latest_revision = answer.get_latest_revision()
- if request.method == "POST":
- if 'select_revision' in request.POST:
- # user has changed revistion number
- revision_form = RevisionForm(answer, latest_revision, request.POST)
- if revision_form.is_valid():
- # Replace with those from the selected revision
- form = EditAnswerForm(answer,
- AnswerRevision.objects.get(answer=answer,
- revision=revision_form.cleaned_data['revision']))
- else:
- form = EditAnswerForm(answer, latest_revision, request.POST)
- else:
- form = EditAnswerForm(answer, latest_revision, request.POST)
- if form.is_valid():
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- if form.has_changed():
- edited_at = datetime.datetime.now()
- updated_fields = {
- 'last_edited_at': edited_at,
- 'last_edited_by': request.user,
- 'html': html,
- }
- Answer.objects.filter(id=answer.id).update(**updated_fields)
-
- revision = AnswerRevision(
- answer=answer,
- author=request.user,
- revised_at=edited_at,
- text=form.cleaned_data['text']
- )
-
- if form.cleaned_data['summary']:
- revision.summary = form.cleaned_data['summary']
- else:
- revision.summary = 'No.%s Revision' % latest_revision.revision
- revision.save()
-
- answer.question.last_activity_at = edited_at
- answer.question.last_activity_by = request.user
- answer.question.save()
-
- return HttpResponseRedirect(answer.get_absolute_url())
- else:
- revision_form = RevisionForm(answer, latest_revision)
- form = EditAnswerForm(answer, latest_revision)
- return render_to_response('answer_edit.html', {
- 'answer': answer,
- 'revision_form': revision_form,
- 'form': form,
- }, context_instance=RequestContext(request))
-
-
-def answer(request, id):#process a new answer
- question = get_object_or_404(Question, id=id)
- if request.method == "POST":
- form = AnswerForm(question, request.user, request.POST)
- if form.is_valid():
- wiki = form.cleaned_data['wiki']
- text = form.cleaned_data['text']
- update_time = datetime.datetime.now()
-
- if request.user.is_authenticated():
- create_new_answer(
- question=question,
- author=request.user,
- added_at=update_time,
- wiki=wiki,
- text=text,
- email_notify=form.cleaned_data['email_notify']
- )
- else:
- request.session.flush()
- html = sanitize_html(markdowner.convert(text))
- summary = strip_tags(html)[:120]
- anon = AnonymousAnswer(
- question=question,
- wiki=wiki,
- text=text,
- summary=summary,
- session_key=request.session.session_key,
- ip_addr=request.META['REMOTE_ADDR'],
- )
- anon.save()
- return HttpResponseRedirect(reverse('user_signin_new_answer'))
-
- return HttpResponseRedirect(question.get_absolute_url())
-
-def question_comments(request, id):#ajax handler for loading comments to question
- question = get_object_or_404(Question, id=id)
- user = request.user
- return __comments(request, question, 'question')
-
-def answer_comments(request, id):#ajax handler for loading comments on answer
- answer = get_object_or_404(Answer, id=id)
- user = request.user
- return __comments(request, answer, 'answer')
-
-def __comments(request, obj, type):#non-view generic ajax handler to load comments to an object
- # only support get post comments by ajax now
- user = request.user
- if request.is_ajax():
- if request.method == "GET":
- response = __generate_comments_json(obj, type, user)
- elif request.method == "POST":
- if auth.can_add_comments(user,obj):
- comment_data = request.POST.get('comment')
- comment = Comment(content_object=obj, comment=comment_data, user=request.user)
- comment.save()
- obj.comment_count = obj.comment_count + 1
- obj.save()
- response = __generate_comments_json(obj, type, user)
- else:
- response = HttpResponseForbidden(mimetype="application/json")
- return response
-
-def __generate_comments_json(obj, type, user):#non-view generates json data for the post comments
- comments = obj.comments.all().order_by('id')
- # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
- json_comments = []
- from forum.templatetags.extra_tags import diff_date
- for comment in comments:
- comment_user = comment.user
- delete_url = ""
- if user != None and auth.can_delete_comment(user, comment):
- #/posts/392845/comments/219852/delete
- #todo translate this url
- delete_url = reverse(index) + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
- json_comments.append({"id" : comment.id,
- "object_id" : obj.id,
- "comment_age" : diff_date(comment.added_at),
- "text" : comment.comment,
- "user_display_name" : comment_user.username,
- "user_url" : comment_user.get_profile_url(),
- "delete_url" : delete_url
- })
-
- data = simplejson.dumps(json_comments)
- return HttpResponse(data, mimetype="application/json")
-
-def delete_comment(request, object_id='', comment_id='', commented_object_type=None):#ajax handler to delete comment
- response = None
- commented_object = None
- if commented_object_type == 'question':
- commented_object = Question
- elif commented_object_type == 'answer':
- commented_object = Answer
-
- if request.is_ajax():
- comment = get_object_or_404(Comment, id=comment_id)
- if auth.can_delete_comment(request.user, comment):
- obj = get_object_or_404(commented_object, id=object_id)
- obj.comments.remove(comment)
- obj.comment_count = obj.comment_count - 1
- obj.save()
- user = request.user
- return __generate_comments_json(obj, commented_object_type, user)
- raise PermissionDenied()
-
-
-def vote(request, id):#refactor - pretty incomprehensible view used by various ajax calls
-#issues: this subroutine is too long, contains many magic numbers and other issues
-#it's called "vote" but many actions processed here have nothing to do with voting
- """
- vote_type:
- acceptAnswer : 0,
- questionUpVote : 1,
- questionDownVote : 2,
- favorite : 4,
- answerUpVote: 5,
- answerDownVote:6,
- offensiveQuestion : 7,
- offensiveAnswer:8,
- removeQuestion: 9,
- removeAnswer:10
- questionSubscribeUpdates:11
-
- accept answer code:
- response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default
- response_data['success'] = 0, failed 1, Success - by default
- response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel)
-
- vote code:
- allowed = -3, Don't have enough votes left
- -2, Don't have enough reputation score
- -1, Vote his own post
- 0, no allowed - Anonymous
- 1, Allowed - by default
- status = 0, By default
- 1, Cancel
- 2, Vote is too old to be canceled
-
- offensive code:
- allowed = -3, Don't have enough flags left
- -2, Don't have enough reputation score to do this
- 0, not allowed
- 1, allowed
- status = 0, by default
- 1, can't do it again
- """
- response_data = {
- "allowed": 1,
- "success": 1,
- "status" : 0,
- "count" : 0,
- "message" : ''
- }
-
- def __can_vote(vote_score, user):#refactor - belongs to auth.py
- if vote_score == 1:#refactor magic number
- return auth.can_vote_up(request.user)
- else:
- return auth.can_vote_down(request.user)
-
- try:
- if not request.user.is_authenticated():
- response_data['allowed'] = 0
- response_data['success'] = 0
-
- elif request.is_ajax() and request.method == 'POST':
- question = get_object_or_404(Question, id=id)
- vote_type = request.POST.get('type')
-
- #accept answer
- if vote_type == '0':
- answer_id = request.POST.get('postId')
- answer = get_object_or_404(Answer, id=answer_id)
- # make sure question author is current user
- if question.author == request.user:
- # answer user who is also question author is not allow to accept answer
- if answer.author == question.author:
- response_data['success'] = 0
- response_data['allowed'] = -1
- # check if answer has been accepted already
- elif answer.accepted:
- onAnswerAcceptCanceled(answer, request.user)
- response_data['status'] = 1
- else:
- # set other answers in this question not accepted first
- for answer_of_question in Answer.objects.get_answers_from_question(question, request.user):
- if answer_of_question != answer and answer_of_question.accepted:
- onAnswerAcceptCanceled(answer_of_question, request.user)
-
- #make sure retrieve data again after above author changes, they may have related data
- answer = get_object_or_404(Answer, id=answer_id)
- onAnswerAccept(answer, request.user)
- else:
- response_data['allowed'] = 0
- response_data['success'] = 0
- # favorite
- elif vote_type == '4':
- has_favorited = False
- fav_questions = FavoriteQuestion.objects.filter(question=question)
- # if the same question has been favorited before, then delete it
- if fav_questions is not None:
- for item in fav_questions:
- if item.user == request.user:
- item.delete()
- response_data['status'] = 1
- response_data['count'] = len(fav_questions) - 1
- if response_data['count'] < 0:
- response_data['count'] = 0
- has_favorited = True
- # if above deletion has not been executed, just insert a new favorite question
- if not has_favorited:
- new_item = FavoriteQuestion(question=question, user=request.user)
- new_item.save()
- response_data['count'] = FavoriteQuestion.objects.filter(question=question).count()
- Question.objects.update_favorite_count(question)
-
- elif vote_type in ['1', '2', '5', '6']:
- post_id = id
- post = question
- vote_score = 1
- if vote_type in ['5', '6']:
- answer_id = request.POST.get('postId')
- answer = get_object_or_404(Answer, id=answer_id)
- post_id = answer_id
- post = answer
- if vote_type in ['2', '6']:
- vote_score = -1
-
- if post.author == request.user:
- response_data['allowed'] = -1
- elif not __can_vote(vote_score, request.user):
- response_data['allowed'] = -2
- elif post.votes.filter(user=request.user).count() > 0:
- vote = post.votes.filter(user=request.user)[0]
- # unvote should be less than certain time
- if (datetime.datetime.now().day - vote.voted_at.day) >= VOTE_RULES['scope_deny_unvote_days']:
- response_data['status'] = 2
- else:
- voted = vote.vote
- if voted > 0:
- # cancel upvote
- onUpVotedCanceled(vote, post, request.user)
-
- else:
- # cancel downvote
- onDownVotedCanceled(vote, post, request.user)
-
- response_data['status'] = 1
- response_data['count'] = post.score
- elif Vote.objects.get_votes_count_today_from_user(request.user) >= VOTE_RULES['scope_votes_per_user_per_day']:
- response_data['allowed'] = -3
- else:
- vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
- if vote_score > 0:
- # upvote
- onUpVoted(vote, post, request.user)
- else:
- # downvote
- onDownVoted(vote, post, request.user)
-
- votes_left = VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user)
- if votes_left <= VOTE_RULES['scope_warn_votes_left']:
- response_data['message'] = u'%s votes left' % votes_left
- response_data['count'] = post.score
- elif vote_type in ['7', '8']:
- post = question
- post_id = id
- if vote_type == '8':
- post_id = request.POST.get('postId')
- post = get_object_or_404(Answer, id=post_id)
-
- if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= VOTE_RULES['scope_flags_per_user_per_day']:
- response_data['allowed'] = -3
- elif not auth.can_flag_offensive(request.user):
- response_data['allowed'] = -2
- elif post.flagged_items.filter(user=request.user).count() > 0:
- response_data['status'] = 1
- else:
- item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now())
- onFlaggedItem(item, post, request.user)
- response_data['count'] = post.offensive_flag_count
- # send signal when question or answer be marked offensive
- mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user)
- elif vote_type in ['9', '10']:
- post = question
- post_id = id
- if vote_type == '10':
- post_id = request.POST.get('postId')
- post = get_object_or_404(Answer, id=post_id)
-
- if not auth.can_delete_post(request.user, post):
- response_data['allowed'] = -2
- elif post.deleted == True:
- logging.debug('debug restoring post in view')
- onDeleteCanceled(post, request.user)
- response_data['status'] = 1
- else:
- onDeleted(post, request.user)
- delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user)
- elif vote_type == '11':#subscribe q updates
- user = request.user
- if user.is_authenticated():
- if user not in question.followed_by.all():
- question.followed_by.add(user)
- if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False:
- response_data['message'] = \
- _('subscription saved, %(email)s needs validation, see %(details_url)s') \
- % {'email':user.email,'details_url':reverse('faq') + '#validate'}
- feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type='q_sel')
- if feed_setting.frequency == 'n':
- feed_setting.frequency = 'd'
- feed_setting.save()
- if 'message' in response_data:
- response_data['message'] += '<br/>'
- response_data['message'] = _('email update frequency has been set to daily')
- #response_data['status'] = 1
- #responst_data['allowed'] = 1
- else:
- pass
- #response_data['status'] = 0
- #response_data['allowed'] = 0
- elif vote_type == '12':#unsubscribe q updates
- user = request.user
- if user.is_authenticated():
- if user in question.followed_by.all():
- question.followed_by.remove(user)
- else:
- response_data['success'] = 0
- response_data['message'] = u'Request mode is not supported. Please try again.'
-
- data = simplejson.dumps(response_data)
-
- except Exception, e:
- response_data['message'] = str(e)
- data = simplejson.dumps(response_data)
- return HttpResponse(data, mimetype="application/json")
-
-#internally grouped views - used by the tagging system
-@ajax_login_required
-def mark_tag(request, tag=None, **kwargs):#tagging system
- action = kwargs['action']
- ts = MarkedTag.objects.filter(user=request.user, tag__name=tag)
- if action == 'remove':
- logging.debug('deleting tag %s' % tag)
- ts.delete()
- else:
- reason = kwargs['reason']
- if len(ts) == 0:
- try:
- t = Tag.objects.get(name=tag)
- mt = MarkedTag(user=request.user, reason=reason, tag=t)
- mt.save()
- except:
- pass
- else:
- ts.update(reason=reason)
- return HttpResponse(simplejson.dumps(''), mimetype="application/json")
-
-@ajax_login_required
-def ajax_toggle_ignored_questions(request):#ajax tagging and tag-filtering system
- if request.user.hide_ignored_questions:
- new_hide_setting = False
- else:
- new_hide_setting = True
- request.user.hide_ignored_questions = new_hide_setting
- request.user.save()
-
-@ajax_method
-def ajax_command(request):#refactor? view processing ajax commands - note "vote" and view others do it too
- if 'command' not in request.POST:
- return HttpResponseForbidden(mimetype="application/json")
- if request.POST['command'] == 'toggle-ignored-questions':
- return ajax_toggle_ignored_questions(request)
-
diff --git a/forum/views/meta.py b/forum/views/meta.py
index b7a1183c..b4c7a37f 100644
--- a/forum/views/meta.py
+++ b/forum/views/meta.py
@@ -3,9 +3,10 @@ from django.core.urlresolvers import reverse
from django.template import RequestContext
from django.http import HttpResponseRedirect, HttpResponse
from forum.forms import FeedbackForm
+from django.core.urlresolvers import reverse
from django.core.mail import mail_admins
from django.utils.translation import ugettext as _
-from utils.forms import get_next_url
+from forum.utils.forms import get_next_url
from forum.models import Badge, Award
def about(request):
@@ -60,8 +61,8 @@ def badges(request):#user status/reputation system
badges = Badge.objects.all().order_by('type')
my_badges = []
if request.user.is_authenticated():
- my_badges = Award.objects.filter(user=request.user)
- my_badges.query.group_by = ['badge_id']
+ my_badges = Award.objects.filter(user=request.user).values('badge_id')
+ #my_badges.query.group_by = ['badge_id']
return render_to_response('badges.html', {
'badges' : badges,
@@ -88,11 +89,3 @@ def badge(request, id):
'badge' : badge,
}, context_instance=RequestContext(request))
-#osqa-user communication system
-def read_message(request):#marks message a read
- if request.method == "POST":
- if request.POST['formdata'] == 'required':
- request.session['message_silent'] = 1
- if request.user.is_authenticated():
- request.user.delete_messages()
- return HttpResponse('')
diff --git a/forum/views/readers.py b/forum/views/readers.py
new file mode 100644
index 00000000..6b0da476
--- /dev/null
+++ b/forum/views/readers.py
@@ -0,0 +1,588 @@
+# encoding:utf-8
+import datetime
+import logging
+from urllib import unquote
+from django.conf import settings
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
+from django.core.paginator import Paginator, EmptyPage, InvalidPage
+from django.template import RequestContext
+from django.utils.html import *
+from django.utils import simplejson
+from django.db.models import Q
+from django.utils.translation import ugettext as _
+from django.template.defaultfilters import slugify
+from django.core.urlresolvers import reverse
+from django.utils.datastructures import SortedDict
+
+from forum.utils.html import sanitize_html
+from markdown2 import Markdown
+#from lxml.html.diff import htmldiff
+from forum.utils.diff import textDiff as htmldiff
+from forum.forms import *
+from forum.models import *
+from forum.auth import *
+from forum.const import *
+from forum import auth
+from forum.utils.forms import get_next_url
+
+# used in index page
+#refactor - move these numbers somewhere?
+INDEX_PAGE_SIZE = 30
+INDEX_AWARD_SIZE = 15
+INDEX_TAGS_SIZE = 25
+# used in tags list
+DEFAULT_PAGE_SIZE = 60
+# used in questions
+QUESTIONS_PAGE_SIZE = 30
+# used in answers
+ANSWERS_PAGE_SIZE = 10
+
+markdowner = Markdown(html4tags=True)
+
+#system to display main content
+def _get_tags_cache_json():#service routine used by views requiring tag list in the javascript space
+ """returns list of all tags in json format
+ no caching yet, actually
+ """
+ tags = Tag.objects.filter(deleted=False).all()
+ tags_list = []
+ for tag in tags:
+ dic = {'n': tag.name, 'c': tag.used_count}
+ tags_list.append(dic)
+ tags = simplejson.dumps(tags_list)
+ return tags
+
+def _get_and_remember_questions_sort_method(request, view_dic, default):#service routine used by q listing views and question view
+ """manages persistence of post sort order
+ it is assumed that when user wants newest question -
+ then he/she wants newest answers as well, etc.
+ how far should this assumption actually go - may be a good question
+ """
+ if default not in view_dic:
+ raise Exception('default value must be in view_dic')
+
+ q_sort_method = request.REQUEST.get('sort', None)
+ if q_sort_method == None:
+ q_sort_method = request.session.get('questions_sort_method', default)
+
+ if q_sort_method not in view_dic:
+ q_sort_method = default
+ request.session['questions_sort_method'] = q_sort_method
+ return q_sort_method, view_dic[q_sort_method]
+
+#refactor? - we have these
+#views that generate a listing of questions in one way or another:
+#index, unanswered, questions, search, tag
+#should we dry them up?
+#related topics - information drill-down, search refinement
+
+def index(request):#generates front page - shows listing of questions sorted in various ways
+ """index view mapped to the root url of the Q&A site
+ """
+ view_dic = {
+ "latest":"-last_activity_at",
+ "hottest":"-answer_count",
+ "mostvoted":"-score",
+ }
+ view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest')
+
+ pagesize = request.session.get("pagesize",QUESTIONS_PAGE_SIZE)
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ qs = Question.objects.exclude(deleted=True).order_by(orderby)
+
+ objects_list = Paginator(qs, pagesize)
+ questions = objects_list.page(page)
+
+ # RISK - inner join queries
+ #questions = questions.select_related()
+ tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE)
+
+ awards = Award.objects.get_recent_awards()
+
+ (interesting_tag_names, ignored_tag_names) = (None, None)
+ if request.user.is_authenticated():
+ pt = MarkedTag.objects.filter(user=request.user)
+ interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
+ ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
+
+ tags_autocomplete = _get_tags_cache_json()
+
+ return render_to_response('index.html', {
+ 'interesting_tag_names': interesting_tag_names,
+ 'tags_autocomplete': tags_autocomplete,
+ 'ignored_tag_names': ignored_tag_names,
+ "questions" : questions,
+ "tab_id" : view_id,
+ "tags" : tags,
+ "awards" : awards[:INDEX_AWARD_SIZE],
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
+
+def unanswered(request):#generates listing of unanswered questions
+ return questions(request, unanswered=True)
+
+def questions(request, tagname=None, unanswered=False):#a view generating listing of questions, used by 'unanswered' too
+ """
+ List of Questions, Tagged questions, and Unanswered questions.
+ """
+ # template file
+ # "questions.html" or maybe index.html in the future
+ template_file = "questions.html"
+ # Set flag to False by default. If it is equal to True, then need to be saved.
+ pagesize_changed = False
+ # get pagesize from session, if failed then get default value
+ pagesize = request.session.get("pagesize",QUESTIONS_PAGE_SIZE)
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ view_id, orderby = _get_and_remember_questions_sort_method(request,view_dic,'latest')
+
+ # check if request is from tagged questions
+ qs = Question.objects.exclude(deleted=True)
+
+ if tagname is not None:
+ qs = qs.filter(tags__name = unquote(tagname))
+
+ if unanswered:
+ qs = qs.exclude(answer_accepted=True)
+
+ author_name = None
+ #user contributed questions & answers
+ if 'user' in request.GET:
+ try:
+ author_name = request.GET['user']
+ u = User.objects.get(username=author_name)
+ qs = qs.filter(Q(author=u) | Q(answers__author=u))
+ except User.DoesNotExist:
+ author_name = None
+
+ if request.user.is_authenticated():
+ uid_str = str(request.user.id)
+ qs = qs.extra(
+ select = SortedDict([
+ (
+ 'interesting_score',
+ 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
+ + 'WHERE forum_markedtag.user_id = %s '
+ + 'AND forum_markedtag.tag_id = question_tags.tag_id '
+ + 'AND forum_markedtag.reason = \'good\' '
+ + 'AND question_tags.question_id = question.id'
+ ),
+ ]),
+ select_params = (uid_str,),
+ )
+ if request.user.hide_ignored_questions:
+ ignored_tags = Tag.objects.filter(user_selections__reason='bad',
+ user_selections__user = request.user)
+ qs = qs.exclude(tags__in=ignored_tags)
+ else:
+ qs = qs.extra(
+ select = SortedDict([
+ (
+ 'ignored_score',
+ 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
+ + 'WHERE forum_markedtag.user_id = %s '
+ + 'AND forum_markedtag.tag_id = question_tags.tag_id '
+ + 'AND forum_markedtag.reason = \'bad\' '
+ + 'AND question_tags.question_id = question.id'
+ )
+ ]),
+ select_params = (uid_str, )
+ )
+
+ qs = qs.select_related(depth=1).order_by(orderby)
+
+ objects_list = Paginator(qs, pagesize)
+ questions = objects_list.page(page)
+
+ # Get related tags from this page objects
+ if questions.object_list.count() > 0:
+ related_tags = Tag.objects.get_tags_by_questions(questions.object_list)
+ else:
+ related_tags = None
+ tags_autocomplete = _get_tags_cache_json()
+
+ # get the list of interesting and ignored tags
+ (interesting_tag_names, ignored_tag_names) = (None, None)
+ if request.user.is_authenticated():
+ pt = MarkedTag.objects.filter(user=request.user)
+ interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
+ ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
+
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "author_name" : author_name,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "tags_autocomplete" : tags_autocomplete,
+ "searchtag" : tagname,
+ "is_unanswered" : unanswered,
+ "interesting_tag_names": interesting_tag_names,
+ 'ignored_tag_names': ignored_tag_names,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
+
+def search(request): #generates listing of questions matching a search query - including tags and just words
+ """generates listing of questions matching a search query
+ supports full text search in mysql db using sphinx and internally in postgresql
+ falls back on simple partial string matching approach if
+ full text search function is not available
+ """
+ if request.method == "GET":
+ keywords = request.GET.get("q")
+ search_type = request.GET.get("t")
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+ if keywords is None:
+ return HttpResponseRedirect(reverse(index))
+ if search_type == 'tag':
+ return HttpResponseRedirect(reverse('tags') + '?q=%s&page=%s' % (keywords.strip(), page))
+ elif search_type == "user":
+ return HttpResponseRedirect(reverse('users') + '?q=%s&page=%s' % (keywords.strip(), page))
+ elif search_type == "question":
+
+ template_file = "questions.html"
+ # Set flag to False by default. If it is equal to True, then need to be saved.
+ pagesize_changed = False
+ # get pagesize from session, if failed then get default value
+ user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
+ # set pagesize equal to logon user specified value in database
+ if request.user.is_authenticated() and request.user.questions_per_page > 0:
+ user_page_size = request.user.questions_per_page
+
+ try:
+ page = int(request.GET.get('page', '1'))
+ # get new pagesize from UI selection
+ pagesize = int(request.GET.get('pagesize', user_page_size))
+ if pagesize <> user_page_size:
+ pagesize_changed = True
+
+ except ValueError:
+ page = 1
+ pagesize = user_page_size
+
+ # save this pagesize to user database
+ if pagesize_changed:
+ request.session["pagesize"] = pagesize
+ if request.user.is_authenticated():
+ user = request.user
+ user.questions_per_page = pagesize
+ user.save()
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-added_at"
+
+ def question_search(keywords, orderby):
+ objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
+ # RISK - inner join queries
+ return objects.select_related();
+
+ from forum.modules import get_handler
+
+ question_search = get_handler('question_search', question_search)
+
+ objects = question_search(keywords, orderby)
+
+ objects_list = Paginator(objects, pagesize)
+ questions = objects_list.page(page)
+
+ # Get related tags from this page objects
+ related_tags = []
+ for question in questions.object_list:
+ tags = list(question.tags.all())
+ for tag in tags:
+ if tag not in related_tags:
+ related_tags.append(tag)
+
+ #if is_search is true in the context, prepend this string to soting tabs urls
+ search_uri = "?q=%s&page=%d&t=question" % ("+".join(keywords.split()), page)
+
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "searchtag" : None,
+ "searchtitle" : keywords,
+ "keywords" : keywords,
+ "is_unanswered" : False,
+ "is_search": True,
+ "search_uri": search_uri,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
+
+ else:
+ raise Http404
+
+def tag(request, tag):#stub generates listing of questions tagged with a single tag
+ return questions(request, tagname=tag)
+
+def tags(request):#view showing a listing of available tags - plain list
+ stag = ""
+ is_paginated = True
+ sortby = request.GET.get('sort', 'used')
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ if request.method == "GET":
+ stag = request.GET.get("q", "").strip()
+ if stag != '':
+ objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE)
+ else:
+ if sortby == "name":
+ objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE)
+ else:
+ objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE)
+
+ try:
+ tags = objects_list.page(page)
+ except (EmptyPage, InvalidPage):
+ tags = objects_list.page(objects_list.num_pages)
+
+ return render_to_response('tags.html', {
+ "tags" : tags,
+ "stag" : stag,
+ "tab_id" : sortby,
+ "keywords" : stag,
+ "context" : {
+ 'is_paginated' : is_paginated,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': tags.has_previous(),
+ 'has_next': tags.has_next(),
+ 'previous': tags.previous_page_number(),
+ 'next': tags.next_page_number(),
+ 'base_url' : reverse('tags') + '?sort=%s&' % sortby
+ }
+ }, context_instance=RequestContext(request))
+
+def question(request, id):#refactor - long subroutine. display question body, answers and comments
+ """view that displays body of the question and
+ all answers to it
+ """
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ qsm = request.session.get('questions_sort_method',None)
+ if qsm in ('mostvoted','latest'):
+ logging.debug('loaded from session ' + qsm)
+ if qsm == 'mostvoted':
+ view_id = 'votes'
+ orderby = '-score'
+ else:
+ view_id = 'latest'
+ orderby = '-added_at'
+ else:
+ view_id = "votes"
+ orderby = "-score"
+
+ logging.debug('view_id=' + str(view_id))
+
+ question = get_object_or_404(Question, id=id)
+ try:
+ pattern = r'/%s%s%d/([\w-]+)' % (settings.FORUM_SCRIPT_ALIAS,_('question/'), question.id)
+ path_re = re.compile(pattern)
+ logging.debug(pattern)
+ logging.debug(request.path)
+ m = path_re.match(request.path)
+ if m:
+ slug = m.group(1)
+ logging.debug('have slug %s' % slug)
+ assert(slug == slugify(question.title))
+ else:
+ logging.debug('no match!')
+ except:
+ return HttpResponseRedirect(question.get_absolute_url())
+
+ if question.deleted and not auth.can_view_deleted_post(request.user, question):
+ raise Http404
+ answer_form = AnswerForm(question,request.user)
+ answers = Answer.objects.get_answers_from_question(question, request.user)
+ answers = answers.select_related(depth=1)
+
+ favorited = question.has_favorite_by_user(request.user)
+ if request.user.is_authenticated():
+ question_vote = question.votes.select_related().filter(user=request.user)
+ else:
+ question_vote = None #is this correct?
+ if question_vote is not None and question_vote.count() > 0:
+ question_vote = question_vote[0]
+
+ user_answer_votes = {}
+ for answer in answers:
+ vote = answer.get_user_vote(request.user)
+ if vote is not None and not user_answer_votes.has_key(answer.id):
+ vote_value = -1
+ if vote.is_upvote():
+ vote_value = 1
+ user_answer_votes[answer.id] = vote_value
+
+ if answers is not None:
+ answers = answers.order_by("-accepted", orderby)
+
+ filtered_answers = []
+ for answer in answers:
+ if answer.deleted == True:
+ if answer.author_id == request.user.id:
+ filtered_answers.append(answer)
+ else:
+ filtered_answers.append(answer)
+
+ objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE)
+ page_objects = objects_list.page(page)
+
+ #todo: merge view counts per user and per session
+ #1) view count per session
+ update_view_count = False
+ if 'question_view_times' not in request.session:
+ request.session['question_view_times'] = {}
+
+ last_seen = request.session['question_view_times'].get(question.id,None)
+ updated_when, updated_who = question.get_last_update_info()
+
+ if updated_who != request.user:
+ if last_seen:
+ if last_seen < updated_when:
+ update_view_count = True
+ else:
+ update_view_count = True
+
+ request.session['question_view_times'][question.id] = datetime.datetime.now()
+
+ if update_view_count:
+ question.view_count += 1
+ question.save()
+
+ #2) question view count per user
+ if request.user.is_authenticated():
+ try:
+ question_view = QuestionView.objects.get(who=request.user, question=question)
+ except QuestionView.DoesNotExist:
+ question_view = QuestionView(who=request.user, question=question)
+ question_view.when = datetime.datetime.now()
+ question_view.save()
+
+ return render_to_response('question.html', {
+ "question" : question,
+ "question_vote" : question_vote,
+ "question_comment_count":question.comments.count(),
+ "answer" : answer_form,
+ "answers" : page_objects.object_list,
+ "user_answer_votes": user_answer_votes,
+ "tags" : question.tags.all(),
+ "tab_id" : view_id,
+ "favorited" : favorited,
+ "similar_questions" : Question.objects.get_similar_questions(question),
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': page_objects.has_previous(),
+ 'has_next': page_objects.has_next(),
+ 'previous': page_objects.previous_page_number(),
+ 'next': page_objects.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'extend_url' : "#sort-top"
+ }
+ }, context_instance=RequestContext(request))
+
+QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n'
+ '<div class="text">%(html)s</div>\n'
+ '<div class="tags">%(tags)s</div>')
+def question_revisions(request, id):
+ post = get_object_or_404(Question, id=id)
+ revisions = list(post.revisions.all())
+ revisions.reverse()
+ for i, revision in enumerate(revisions):
+ revision.html = QUESTION_REVISION_TEMPLATE % {
+ 'title': revision.title,
+ 'html': sanitize_html(markdowner.convert(revision.text)),
+ 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
+ for tag in revision.tagnames.split(' ')]),
+ }
+ if i > 0:
+ revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
+ else:
+ revisions[i].diff = QUESTION_REVISION_TEMPLATE % {
+ 'title': revisions[0].title,
+ 'html': sanitize_html(markdowner.convert(revisions[0].text)),
+ 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
+ for tag in revisions[0].tagnames.split(' ')]),
+ }
+ revisions[i].summary = _('initial version')
+ return render_to_response('revisions_question.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
+ANSWER_REVISION_TEMPLATE = ('<div class="text">%(html)s</div>')
+def answer_revisions(request, id):
+ post = get_object_or_404(Answer, id=id)
+ revisions = list(post.revisions.all())
+ revisions.reverse()
+ for i, revision in enumerate(revisions):
+ revision.html = ANSWER_REVISION_TEMPLATE % {
+ 'html': sanitize_html(markdowner.convert(revision.text))
+ }
+ if i > 0:
+ revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
+ else:
+ revisions[i].diff = revisions[i].text
+ revisions[i].summary = _('initial version')
+ return render_to_response('revisions_answer.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
diff --git a/forum/views/users.py b/forum/views/users.py
index 2c7f5c34..cc05c19e 100644
--- a/forum/views/users.py
+++ b/forum/views/users.py
@@ -1,13 +1,19 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.paginator import Paginator, EmptyPage, InvalidPage
+from django.template.defaultfilters import slugify
+from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.http import HttpResponse, HttpResponseForbidden
from django.utils.translation import ugettext as _
+from django.utils.html import strip_tags
+from django.core.urlresolvers import reverse
from forum.forms import *#incomplete list is EditUserForm, ModerateUserForm, TagFilterSelectionForm,
from forum import auth
+import calendar
+from django.contrib.contenttypes.models import ContentType
question_type = ContentType.objects.get_for_model(Question)
answer_type = ContentType.objects.get_for_model(Answer)
@@ -938,3 +944,4 @@ def user(request, id):
from forum.views import users
func = user_view.view_func
return func(request, id, user_view)
+
diff --git a/forum/views/writers.py b/forum/views/writers.py
new file mode 100644
index 00000000..a8f07334
--- /dev/null
+++ b/forum/views/writers.py
@@ -0,0 +1,442 @@
+# encoding:utf-8
+import os.path
+import time, datetime, random
+import logging
+from django.core.files.storage import default_storage
+from django.shortcuts import render_to_response, get_object_or_404
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
+from django.template import RequestContext
+from django.utils.html import *
+from django.utils import simplejson
+from django.utils.translation import ugettext as _
+from django.core.urlresolvers import reverse
+from django.core.exceptions import PermissionDenied
+
+from forum.utils.html import sanitize_html
+from markdown2 import Markdown
+from forum.forms import *
+from forum.models import *
+from forum.auth import *
+from forum.const import *
+from forum import auth
+from forum.utils.forms import get_next_url
+from forum.views.readers import _get_tags_cache_json
+
+# used in index page
+INDEX_PAGE_SIZE = 20
+INDEX_AWARD_SIZE = 15
+INDEX_TAGS_SIZE = 100
+# used in tags list
+DEFAULT_PAGE_SIZE = 60
+# used in questions
+QUESTIONS_PAGE_SIZE = 10
+# used in answers
+ANSWERS_PAGE_SIZE = 10
+
+markdowner = Markdown(html4tags=True)
+
+def upload(request):#ajax upload file to a question or answer
+ class FileTypeNotAllow(Exception):
+ pass
+ class FileSizeNotAllow(Exception):
+ pass
+ class UploadPermissionNotAuthorized(Exception):
+ pass
+
+ #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>
+ xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>"
+
+ try:
+ f = request.FILES['file-upload']
+ # check upload permission
+ if not auth.can_upload_files(request.user):
+ raise UploadPermissionNotAuthorized
+
+ # check file type
+ file_name_suffix = os.path.splitext(f.name)[1].lower()
+ if not file_name_suffix in settings.ALLOW_FILE_TYPES:
+ raise FileTypeNotAllow
+
+ # generate new file name
+ new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix
+ # use default storage to store file
+ default_storage.save(new_file_name, f)
+ # check file size
+ # byte
+ size = default_storage.size(new_file_name)
+ if size > settings.ALLOW_MAX_FILE_SIZE:
+ default_storage.delete(new_file_name)
+ raise FileSizeNotAllow
+
+ result = xml_template % ('Good', '', default_storage.url(new_file_name))
+ except UploadPermissionNotAuthorized:
+ result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '')
+ except FileTypeNotAllow:
+ result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '')
+ except FileSizeNotAllow:
+ result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
+ except Exception:
+ result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '')
+
+ return HttpResponse(result, mimetype="application/xml")
+
+#@login_required #actually you can post anonymously, but then must register
+def ask(request):#view used to ask a new question
+ """a view to ask a new question
+ gives space for q title, body, tags and checkbox for to post as wiki
+
+ user can start posting a question anonymously but then
+ must login/register in order for the question go be shown
+ """
+ if request.method == "POST":
+ form = AskForm(request.POST)
+ if form.is_valid():
+
+ added_at = datetime.datetime.now()
+ title = strip_tags(form.cleaned_data['title'].strip())
+ wiki = form.cleaned_data['wiki']
+ tagnames = form.cleaned_data['tags'].strip()
+ text = form.cleaned_data['text']
+ html = sanitize_html(markdowner.convert(text))
+ summary = strip_tags(html)[:120]
+
+ if request.user.is_authenticated():
+ author = request.user
+
+ question = Question.objects.create_new(
+ title = title,
+ author = author,
+ added_at = added_at,
+ wiki = wiki,
+ tagnames = tagnames,
+ summary = summary,
+ text = sanitize_html(markdowner.convert(text))
+ )
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ request.session.flush()
+ session_key = request.session.session_key
+ question = AnonymousQuestion(
+ session_key = session_key,
+ title = title,
+ tagnames = tagnames,
+ wiki = wiki,
+ text = text,
+ summary = summary,
+ added_at = added_at,
+ ip_addr = request.META['REMOTE_ADDR'],
+ )
+ question.save()
+ return HttpResponseRedirect(reverse('user_signin_new_question'))
+ else:
+ form = AskForm()
+
+ tags = _get_tags_cache_json()
+ return render_to_response('ask.html', {
+ 'form' : form,
+ 'tags' : tags,
+ 'email_validation_faq_url':reverse('faq') + '#validate',
+ }, context_instance=RequestContext(request))
+
+@login_required
+def edit_question(request, id):#edit or retag a question
+ """view to edit question
+ """
+ question = get_object_or_404(Question, id=id)
+ if question.deleted and not auth.can_view_deleted_post(request.user, question):
+ raise Http404
+ if auth.can_edit_post(request.user, question):
+ return _edit_question(request, question)
+ elif auth.can_retag_questions(request.user):
+ return _retag_question(request, question)
+ else:
+ raise Http404
+
+def _retag_question(request, question):#non-url subview of edit question - just retag
+ """retag question sub-view used by
+ view "edit_question"
+ """
+ if request.method == 'POST':
+ form = RetagQuestionForm(question, request.POST)
+ if form.is_valid():
+ if form.has_changed():
+ latest_revision = question.get_latest_revision()
+ retagged_at = datetime.datetime.now()
+ # Update the Question itself
+ Question.objects.filter(id=question.id).update(
+ tagnames = form.cleaned_data['tags'],
+ last_edited_at = retagged_at,
+ last_edited_by = request.user,
+ last_activity_at = retagged_at,
+ last_activity_by = request.user
+ )
+ # Update the Question's tag associations
+ tags_updated = Question.objects.update_tags(question,
+ form.cleaned_data['tags'], request.user)
+ # Create a new revision
+ QuestionRevision.objects.create(
+ question = question,
+ title = latest_revision.title,
+ author = request.user,
+ revised_at = retagged_at,
+ tagnames = form.cleaned_data['tags'],
+ summary = CONST['retagged'],
+ text = latest_revision.text
+ )
+ # send tags updated singal
+ tags_updated.send(sender=question.__class__, question=question)
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = RetagQuestionForm(question)
+ return render_to_response('question_retag.html', {
+ 'question': question,
+ 'form' : form,
+ 'tags' : _get_tags_cache_json(),
+ }, context_instance=RequestContext(request))
+
+def _edit_question(request, question):#non-url subview of edit_question - just edit the body/title
+ latest_revision = question.get_latest_revision()
+ revision_form = None
+ if request.method == 'POST':
+ if 'select_revision' in request.POST:
+ # user has changed revistion number
+ revision_form = RevisionForm(question, latest_revision, request.POST)
+ if revision_form.is_valid():
+ # Replace with those from the selected revision
+ form = EditQuestionForm(question,
+ QuestionRevision.objects.get(question=question,
+ revision=revision_form.cleaned_data['revision']))
+ else:
+ form = EditQuestionForm(question, latest_revision, request.POST)
+ else:
+ # Always check modifications against the latest revision
+ form = EditQuestionForm(question, latest_revision, request.POST)
+ if form.is_valid():
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ if form.has_changed():
+ edited_at = datetime.datetime.now()
+ tags_changed = (latest_revision.tagnames !=
+ form.cleaned_data['tags'])
+ tags_updated = False
+ # Update the Question itself
+ updated_fields = {
+ 'title': form.cleaned_data['title'],
+ 'last_edited_at': edited_at,
+ 'last_edited_by': request.user,
+ 'last_activity_at': edited_at,
+ 'last_activity_by': request.user,
+ 'tagnames': form.cleaned_data['tags'],
+ 'summary': strip_tags(html)[:120],
+ 'html': html,
+ }
+
+ # only save when it's checked
+ # because wiki doesn't allow to be edited if last version has been enabled already
+ # and we make sure this in forms.
+ if ('wiki' in form.cleaned_data and
+ form.cleaned_data['wiki']):
+ updated_fields['wiki'] = True
+ updated_fields['wikified_at'] = edited_at
+
+ Question.objects.filter(
+ id=question.id).update(**updated_fields)
+ # Update the Question's tag associations
+ if tags_changed:
+ tags_updated = Question.objects.update_tags(
+ question, form.cleaned_data['tags'], request.user)
+ # Create a new revision
+ revision = QuestionRevision(
+ question = question,
+ title = form.cleaned_data['title'],
+ author = request.user,
+ revised_at = edited_at,
+ tagnames = form.cleaned_data['tags'],
+ text = form.cleaned_data['text'],
+ )
+ if form.cleaned_data['summary']:
+ revision.summary = form.cleaned_data['summary']
+ else:
+ revision.summary = 'No.%s Revision' % latest_revision.revision
+ revision.save()
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+
+ revision_form = RevisionForm(question, latest_revision)
+ form = EditQuestionForm(question, latest_revision)
+ return render_to_response('question_edit.html', {
+ 'question': question,
+ 'revision_form': revision_form,
+ 'form' : form,
+ 'tags' : _get_tags_cache_json()
+ }, context_instance=RequestContext(request))
+
+@login_required
+def edit_answer(request, id):
+ answer = get_object_or_404(Answer, id=id)
+ if answer.deleted and not auth.can_view_deleted_post(request.user, answer):
+ raise Http404
+ elif not auth.can_edit_post(request.user, answer):
+ raise Http404
+ else:
+ latest_revision = answer.get_latest_revision()
+ if request.method == "POST":
+ if 'select_revision' in request.POST:
+ # user has changed revistion number
+ revision_form = RevisionForm(answer, latest_revision, request.POST)
+ if revision_form.is_valid():
+ # Replace with those from the selected revision
+ form = EditAnswerForm(answer,
+ AnswerRevision.objects.get(answer=answer,
+ revision=revision_form.cleaned_data['revision']))
+ else:
+ form = EditAnswerForm(answer, latest_revision, request.POST)
+ else:
+ form = EditAnswerForm(answer, latest_revision, request.POST)
+ if form.is_valid():
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ if form.has_changed():
+ edited_at = datetime.datetime.now()
+ updated_fields = {
+ 'last_edited_at': edited_at,
+ 'last_edited_by': request.user,
+ 'html': html,
+ }
+ Answer.objects.filter(id=answer.id).update(**updated_fields)
+
+ revision = AnswerRevision(
+ answer=answer,
+ author=request.user,
+ revised_at=edited_at,
+ text=form.cleaned_data['text']
+ )
+
+ if form.cleaned_data['summary']:
+ revision.summary = form.cleaned_data['summary']
+ else:
+ revision.summary = 'No.%s Revision' % latest_revision.revision
+ revision.save()
+
+ answer.question.last_activity_at = edited_at
+ answer.question.last_activity_by = request.user
+ answer.question.save()
+
+ return HttpResponseRedirect(answer.get_absolute_url())
+ else:
+ revision_form = RevisionForm(answer, latest_revision)
+ form = EditAnswerForm(answer, latest_revision)
+ return render_to_response('answer_edit.html', {
+ 'answer': answer,
+ 'revision_form': revision_form,
+ 'form': form,
+ }, context_instance=RequestContext(request))
+
+def answer(request, id):#process a new answer
+ question = get_object_or_404(Question, id=id)
+ if request.method == "POST":
+ form = AnswerForm(question, request.user, request.POST)
+ if form.is_valid():
+ wiki = form.cleaned_data['wiki']
+ text = form.cleaned_data['text']
+ update_time = datetime.datetime.now()
+
+ if request.user.is_authenticated():
+ Answer.objects.create_new(
+ question=question,
+ author=request.user,
+ added_at=update_time,
+ wiki=wiki,
+ text=sanitize_html(markdowner.convert(text)),
+ email_notify=form.cleaned_data['email_notify']
+ )
+ else:
+ request.session.flush()
+ html = sanitize_html(markdowner.convert(text))
+ summary = strip_tags(html)[:120]
+ anon = AnonymousAnswer(
+ question=question,
+ wiki=wiki,
+ text=text,
+ summary=summary,
+ session_key=request.session.session_key,
+ ip_addr=request.META['REMOTE_ADDR'],
+ )
+ anon.save()
+ return HttpResponseRedirect(reverse('user_signin_new_answer'))
+
+ return HttpResponseRedirect(question.get_absolute_url())
+
+def __generate_comments_json(obj, type, user):#non-view generates json data for the post comments
+ comments = obj.comments.all().order_by('id')
+ # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
+ json_comments = []
+ from forum.templatetags.extra_tags import diff_date
+ for comment in comments:
+ comment_user = comment.user
+ delete_url = ""
+ if user != None and auth.can_delete_comment(user, comment):
+ #/posts/392845/comments/219852/delete
+ #todo translate this url
+ delete_url = reverse('index') + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
+ json_comments.append({"id" : comment.id,
+ "object_id" : obj.id,
+ "comment_age" : diff_date(comment.added_at),
+ "text" : comment.comment,
+ "user_display_name" : comment_user.username,
+ "user_url" : comment_user.get_profile_url(),
+ "delete_url" : delete_url
+ })
+
+ data = simplejson.dumps(json_comments)
+ return HttpResponse(data, mimetype="application/json")
+
+
+def question_comments(request, id):#ajax handler for loading comments to question
+ question = get_object_or_404(Question, id=id)
+ user = request.user
+ return __comments(request, question, 'question')
+
+def answer_comments(request, id):#ajax handler for loading comments on answer
+ answer = get_object_or_404(Answer, id=id)
+ user = request.user
+ return __comments(request, answer, 'answer')
+
+def __comments(request, obj, type):#non-view generic ajax handler to load comments to an object
+ # only support get post comments by ajax now
+ user = request.user
+ if request.is_ajax():
+ if request.method == "GET":
+ response = __generate_comments_json(obj, type, user)
+ elif request.method == "POST":
+ if auth.can_add_comments(user,obj):
+ comment_data = request.POST.get('comment')
+ comment = Comment(content_object=obj, comment=comment_data, user=request.user)
+ comment.save()
+ obj.comment_count = obj.comment_count + 1
+ obj.save()
+ response = __generate_comments_json(obj, type, user)
+ else:
+ response = HttpResponseForbidden(mimetype="application/json")
+ return response
+
+def delete_comment(request, object_id='', comment_id='', commented_object_type=None):#ajax handler to delete comment
+ response = None
+ commented_object = None
+ if commented_object_type == 'question':
+ commented_object = Question
+ elif commented_object_type == 'answer':
+ commented_object = Answer
+
+ if request.is_ajax():
+ comment = get_object_or_404(Comment, id=comment_id)
+ if auth.can_delete_comment(request.user, comment):
+ obj = get_object_or_404(commented_object, id=object_id)
+ obj.comments.remove(comment)
+ obj.comment_count = obj.comment_count - 1
+ obj.save()
+ user = request.user
+ return __generate_comments_json(obj, commented_object_type, user)
+ raise PermissionDenied()
diff --git a/utils/__init__.py b/forum_modules/__init__.py
index e69de29b..e69de29b 100644..100755
--- a/utils/__init__.py
+++ b/forum_modules/__init__.py
diff --git a/forum_modules/books/__init__.py b/forum_modules/books/__init__.py
new file mode 100755
index 00000000..a182c87c
--- /dev/null
+++ b/forum_modules/books/__init__.py
@@ -0,0 +1,3 @@
+NAME = 'Osqa Books'
+DESCRIPTION = "Allows discussion around books."
+CAN_ENABLE = True
diff --git a/forum_modules/books/models.py b/forum_modules/books/models.py
new file mode 100755
index 00000000..a78c0e76
--- /dev/null
+++ b/forum_modules/books/models.py
@@ -0,0 +1,63 @@
+from django.db import models
+from django.contrib.auth.models import User
+from forum.models import Question
+from django.core.urlresolvers import reverse
+from django.utils.http import urlquote as django_urlquote
+from django.template.defaultfilters import slugify
+
+class Book(models.Model):
+ """
+ Model for book info
+ """
+ user = models.ForeignKey(User)
+ title = models.CharField(max_length=255)
+ short_name = models.CharField(max_length=255)
+ author = models.CharField(max_length=255)
+ price = models.DecimalField(max_digits=6, decimal_places=2)
+ pages = models.SmallIntegerField()
+ published_at = models.DateTimeField()
+ publication = models.CharField(max_length=255)
+ cover_img = models.CharField(max_length=255)
+ tagnames = models.CharField(max_length=125)
+ added_at = models.DateTimeField()
+ last_edited_at = models.DateTimeField()
+ questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
+
+ def get_absolute_url(self):
+ return reverse('book', args=[django_urlquote(slugify(self.short_name))])
+
+ def __unicode__(self):
+ return self.title
+
+ class Meta:
+ app_label = 'forum'
+ db_table = u'book'
+
+class BookAuthorInfo(models.Model):
+ """
+ Model for book author info
+ """
+ user = models.ForeignKey(User)
+ book = models.ForeignKey(Book)
+ blog_url = models.CharField(max_length=255)
+ added_at = models.DateTimeField()
+ last_edited_at = models.DateTimeField()
+
+ class Meta:
+ app_label = 'forum'
+ db_table = u'book_author_info'
+
+class BookAuthorRss(models.Model):
+ """
+ Model for book author blog rss
+ """
+ user = models.ForeignKey(User)
+ book = models.ForeignKey(Book)
+ title = models.CharField(max_length=255)
+ url = models.CharField(max_length=255)
+ rss_created_at = models.DateTimeField()
+ added_at = models.DateTimeField()
+
+ class Meta:
+ app_label = 'forum'
+ db_table = u'book_author_rss' \ No newline at end of file
diff --git a/forum_modules/books/urls.py b/forum_modules/books/urls.py
new file mode 100755
index 00000000..bc0811e7
--- /dev/null
+++ b/forum_modules/books/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls.defaults import *
+from django.utils.translation import ugettext as _
+
+import views as app
+
+urlpatterns = patterns('',
+ url(r'^%s$' % _('books/'), app.books, name='books'),
+ url(r'^%s%s(?P<short_name>[^/]+)/$' % (_('books/'), _('ask/')), app.ask_book, name='ask_book'),
+ url(r'^%s(?P<short_name>[^/]+)/$' % _('books/'), app.book, name='book'),
+) \ No newline at end of file
diff --git a/forum/views/books.py b/forum_modules/books/views.py
index bb1bfe85..35e9f0fe 100644..100755
--- a/forum/views/books.py
+++ b/forum_modules/books/views.py
@@ -1,16 +1,20 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
+from django.template import RequestContext
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
-from django.shortcuts import render_to_response
-from forum.models import BookAuthorInfo, BookAuthorRss, Book
-from forum.models import Question, QuestionRevision
-from django.http import HttpResponseRedirect
+from django.utils.html import *
+
+from models import *
+
+from forum.forms import AskForm
+from forum.views.readers import _get_tags_cache_json
+from forum.models import *
+from forum.utils.html import sanitize_html
def books(request):
- """this view seems to redirect to a default book
- maybe it should instead show some popular titles?
- """
- return HttpResponseRedirect(reverse('books') + 'mysql-zhaoyang')
-
+ return HttpResponseRedirect(reverse('books') + '/mysql-zhaoyang')
+
def book(request, short_name, unanswered=False):
"""
1. questions list
@@ -37,7 +41,7 @@ def book(request, short_name, unanswered=False):
# set pagesize equal to logon user specified value in database
if request.user.is_authenticated() and request.user.questions_per_page > 0:
user_page_size = request.user.questions_per_page
-
+
try:
page = int(request.GET.get('page', '1'))
except ValueError:
@@ -50,7 +54,7 @@ def book(request, short_name, unanswered=False):
except KeyError:
view_id = "latest"
orderby = "-added_at"
-
+
# check if request is from tagged questions
if unanswered:
# check if request is from unanswered questions
@@ -118,7 +122,7 @@ def ask_book(request, short_name):
summary = CONST['default_version'],
text = form.cleaned_data['text']
)
-
+
books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
match_count = len(books)
if match_count == 1:
@@ -135,5 +139,4 @@ def ask_book(request, short_name):
'form' : form,
'tags' : tags,
'email_validation_faq_url': reverse('faq') + '#validate',
- }, context_instance=RequestContext(request))
-
+ }, context_instance=RequestContext(request)) \ No newline at end of file
diff --git a/forum_modules/pgfulltext/__init__.py b/forum_modules/pgfulltext/__init__.py
new file mode 100755
index 00000000..8215e1a9
--- /dev/null
+++ b/forum_modules/pgfulltext/__init__.py
@@ -0,0 +1,9 @@
+NAME = 'Postgresql Full Text Search'
+DESCRIPTION = "Enables PostgreSql full text search functionality."
+
+try:
+ import psycopg2
+ CAN_ENABLE = True
+except:
+ CAN_ENABLE = False
+ \ No newline at end of file
diff --git a/forum_modules/pgfulltext/handlers.py b/forum_modules/pgfulltext/handlers.py
new file mode 100755
index 00000000..f4a7a3b2
--- /dev/null
+++ b/forum_modules/pgfulltext/handlers.py
@@ -0,0 +1,11 @@
+from forum.models import Question
+
+def question_search(keywords, orderby):
+ return Question.objects.filter(deleted=False).extra(
+ select={
+ 'ranking': "ts_rank_cd(tsv, plainto_tsquery(%s), 32)",
+ },
+ where=["tsv @@ plainto_tsquery(%s)"],
+ params=[keywords],
+ select_params=[keywords]
+ ).order_by(orderby, '-ranking') \ No newline at end of file
diff --git a/pgfulltext/management.py b/forum_modules/pgfulltext/management.py
index 04303092..487580ff 100644..100755
--- a/pgfulltext/management.py
+++ b/forum_modules/pgfulltext/management.py
@@ -5,7 +5,7 @@ from django.conf import settings
import forum.models
-if settings.USE_PG_FTS:
+if settings.DATABASE_ENGINE in ('postgresql_psycopg2', 'postgresql', ):
from django.db.models.signals import post_syncdb
def setup_pgfulltext(sender, **kwargs):
@@ -15,9 +15,15 @@ if settings.USE_PG_FTS:
post_syncdb.connect(setup_pgfulltext)
def install_pg_fts():
- f = open(os.path.join(os.path.dirname(__file__), '../sql_scripts/pg_fts_install.sql'), 'r')
- cursor = connection.cursor()
- cursor.execute(f.read())
- transaction.commit_unless_managed()
+ f = open(os.path.join(os.path.dirname(__file__), 'pg_fts_install.sql'), 'r')
+
+ try:
+ cursor = connection.cursor()
+ cursor.execute(f.read())
+ transaction.commit_unless_managed()
+ except:
+ pass
+ finally:
+ cursor.close()
+
f.close()
- \ No newline at end of file
diff --git a/forum_modules/pgfulltext/pg_fts_install.sql b/forum_modules/pgfulltext/pg_fts_install.sql
new file mode 100755
index 00000000..72eca516
--- /dev/null
+++ b/forum_modules/pgfulltext/pg_fts_install.sql
@@ -0,0 +1,38 @@
+ALTER TABLE question ADD COLUMN tsv tsvector;
+
+CREATE OR REPLACE FUNCTION public.create_plpgsql_language ()
+ RETURNS TEXT
+ AS $$
+ CREATE LANGUAGE plpgsql;
+ SELECT 'language plpgsql created'::TEXT;
+ $$
+LANGUAGE 'sql';
+
+SELECT CASE WHEN
+ (SELECT true::BOOLEAN
+ FROM pg_language
+ WHERE lanname='plpgsql')
+ THEN
+ (SELECT 'language already installed'::TEXT)
+ ELSE
+ (SELECT public.create_plpgsql_language())
+ END;
+
+DROP FUNCTION public.create_plpgsql_language ();
+
+CREATE OR REPLACE FUNCTION set_question_tsv() RETURNS TRIGGER AS $$
+begin
+ new.tsv :=
+ setweight(to_tsvector('english', coalesce(new.tagnames,'')), 'A') ||
+ setweight(to_tsvector('english', coalesce(new.title,'')), 'B') ||
+ setweight(to_tsvector('english', coalesce(new.summary,'')), 'C');
+ RETURN new;
+end
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
+ON question FOR EACH ROW EXECUTE PROCEDURE set_question_tsv();
+
+ CREATE INDEX question_tsv ON question USING gin(tsv);
+
+UPDATE question SET title = title;
diff --git a/forum_modules/sphinxfulltext/DISABLED b/forum_modules/sphinxfulltext/DISABLED
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/forum_modules/sphinxfulltext/DISABLED
diff --git a/forum_modules/sphinxfulltext/__init__.py b/forum_modules/sphinxfulltext/__init__.py
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/forum_modules/sphinxfulltext/__init__.py
diff --git a/forum_modules/sphinxfulltext/dependencies.py b/forum_modules/sphinxfulltext/dependencies.py
new file mode 100755
index 00000000..046ebfc5
--- /dev/null
+++ b/forum_modules/sphinxfulltext/dependencies.py
@@ -0,0 +1,2 @@
+DJANGO_APPS = ('djangosphinx', )
+
diff --git a/forum_modules/sphinxfulltext/handlers.py b/forum_modules/sphinxfulltext/handlers.py
new file mode 100755
index 00000000..226acf72
--- /dev/null
+++ b/forum_modules/sphinxfulltext/handlers.py
@@ -0,0 +1,4 @@
+from forum.models import Question
+
+def question_search(keywords, orderby):
+ return Question.search.query(keywords) \ No newline at end of file
diff --git a/forum_modules/sphinxfulltext/models.py b/forum_modules/sphinxfulltext/models.py
new file mode 100755
index 00000000..9db4aa86
--- /dev/null
+++ b/forum_modules/sphinxfulltext/models.py
@@ -0,0 +1,10 @@
+from forum.models import Question
+from django.conf import settings
+from djangosphinx.manager import SphinxSearch
+
+
+Question.add_to_class('search', SphinxSearch(
+ index=' '.join(settings.SPHINX_SEARCH_INDICES),
+ mode='SPH_MATCH_ALL',
+ )
+ )
diff --git a/forum_modules/sphinxfulltext/settings.py b/forum_modules/sphinxfulltext/settings.py
new file mode 100755
index 00000000..7c2da124
--- /dev/null
+++ b/forum_modules/sphinxfulltext/settings.py
@@ -0,0 +1,5 @@
+SPHINX_API_VERSION = 0x113 #refer to djangosphinx documentation
+SPHINX_SEARCH_INDICES=('osqa',) #a tuple of index names remember about a comma after the
+#last item, especially if you have just one :)
+SPHINX_SERVER='localhost'
+SPHINX_PORT=3312
diff --git a/junk.py b/junk.py
deleted file mode 100644
index c6c03d27..00000000
--- a/junk.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import os
-
-print os.path.normpath('/haha//haha')
diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo
index b87457df..38120e49 100644
--- a/locale/en/LC_MESSAGES/django.mo
+++ b/locale/en/LC_MESSAGES/django.mo
Binary files differ
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
index 3b8a13cd..bfec60c0 100644
--- a/locale/en/LC_MESSAGES/django.po
+++ b/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-02-16 00:17-0500\n"
+"POT-Creation-Date: 2010-02-08 18:43-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -74,12 +74,12 @@ msgstr ""
#: django_authopenid/forms.py:320
msgid "Incorrect username."
-msgstr ""
+msgstr "sorry, there is no such user name"
#: django_authopenid/urls.py:23 django_authopenid/urls.py:24
#: django_authopenid/urls.py:25 django_authopenid/urls.py:27
#: fbconnect/urls.py:12 fbconnect/urls.py:13 fbconnect/urls.py:14
-#: forum/urls.py:30
+#: forum/urls.py:29
msgid "signin/"
msgstr ""
@@ -149,7 +149,7 @@ msgstr ""
msgid "openid/"
msgstr ""
-#: django_authopenid/urls.py:43 forum/urls.py:50 forum/urls.py:54
+#: django_authopenid/urls.py:43 forum/urls.py:49 forum/urls.py:53
msgid "delete/"
msgstr ""
@@ -168,7 +168,7 @@ msgstr ""
#: django_authopenid/views.py:593
msgid "Welcome email subject line"
-msgstr ""
+msgstr "Welcome to the Q&A forum"
#: django_authopenid/views.py:699
msgid "Password changed."
@@ -178,10 +178,12 @@ msgstr ""
#, python-format
msgid "your email needs to be validated see %(details_url)s"
msgstr ""
+"Your email needs to be validated. Please see details <a "
+"id='validate_email_alert' href='%(details_url)s'>here</a>."
#: django_authopenid/views.py:738
msgid "Email verification subject line"
-msgstr ""
+msgstr "Verification Email from Q&A forum"
#: django_authopenid/views.py:829
msgid "your email was not changed"
@@ -292,7 +294,7 @@ msgstr ""
msgid "question"
msgstr ""
-#: forum/const.py:58 forum/skins/default/templates/book.html:110
+#: forum/const.py:58 templates/book.html:110
msgid "answer"
msgstr ""
@@ -314,7 +316,7 @@ msgstr ""
#: forum/const.py:63
msgid "received award"
-msgstr ""
+msgstr "received badge"
#: forum/const.py:64
msgid "marked best answer"
@@ -368,8 +370,7 @@ msgstr ""
msgid "[deleted]"
msgstr ""
-#: forum/const.py:87 forum/views.py:792 forum/views.py:811
-#: forum/views/content.py:560 forum/views/content.py:579
+#: forum/const.py:87 forum/views.py:796 forum/views.py:815
msgid "initial version"
msgstr ""
@@ -393,10 +394,9 @@ msgstr ""
msgid "latest questions"
msgstr ""
-#: forum/forms.py:18 forum/skins/default/templates/answer_edit_tips.html:35
-#: forum/skins/default/templates/answer_edit_tips.html:39
-#: forum/skins/default/templates/question_edit_tips.html:32
-#: forum/skins/default/templates/question_edit_tips.html:37
+#: forum/forms.py:18 templates/answer_edit_tips.html:35
+#: templates/answer_edit_tips.html.py:39 templates/question_edit_tips.html:32
+#: templates/question_edit_tips.html:37
msgid "title"
msgstr ""
@@ -416,8 +416,7 @@ msgstr ""
msgid "question content must be > 10 characters"
msgstr ""
-#: forum/forms.py:49 forum/skins/default/templates/header.html:28
-#: forum/skins/default/templates/header.html:56
+#: forum/forms.py:49 templates/header.html:28 templates/header.html.py:56
msgid "tags"
msgstr ""
@@ -426,7 +425,7 @@ msgid ""
"Tags are short keywords, with no spaces within. Up to five tags can be used."
msgstr ""
-#: forum/forms.py:58 forum/skins/default/templates/question_retag.html:39
+#: forum/forms.py:58 templates/question_retag.html:39
msgid "tags are required"
msgstr ""
@@ -444,16 +443,11 @@ msgid ""
"characters '.-_#'"
msgstr ""
-#: forum/forms.py:81 forum/skins/default/templates/index.html:62
-#: forum/skins/default/templates/index.html:74
-#: forum/skins/default/templates/post_contributor_info.html:7
-#: forum/skins/default/templates/question_summary_list_roll.html:26
-#: forum/skins/default/templates/question_summary_list_roll.html:38
-#: forum/skins/default/templates/questions.html:97
-#: forum/skins/default/templates/questions.html:109
-#: forum/skins/default/templates/questions.html:366
-#: forum/skins/default/templates/questions.html:378
-#: templates/unanswered.html:51 templates/unanswered.html.py:63
+#: forum/forms.py:81 templates/index.html:61 templates/index.html.py:73
+#: templates/post_contributor_info.html:7
+#: templates/question_summary_list_roll.html:26
+#: templates/question_summary_list_roll.html:38 templates/questions.html:92
+#: templates/questions.html.py:104
msgid "community wiki"
msgstr ""
@@ -489,84 +483,83 @@ msgstr ""
msgid "Your message:"
msgstr ""
-#: forum/forms.py:203
+#: forum/forms.py:202
msgid "this email does not have to be linked to gravatar"
msgstr ""
-#: forum/forms.py:205
+#: forum/forms.py:204
msgid "Screen name"
msgstr ""
-#: forum/forms.py:206
+#: forum/forms.py:205
msgid "Real name"
msgstr ""
-#: forum/forms.py:207
+#: forum/forms.py:206
msgid "Website"
msgstr ""
-#: forum/forms.py:208
+#: forum/forms.py:207
msgid "Location"
msgstr ""
-#: forum/forms.py:209
+#: forum/forms.py:208
msgid "Date of birth"
msgstr ""
-#: forum/forms.py:209
+#: forum/forms.py:208
msgid "will not be shown, used to calculate age, format: YYYY-MM-DD"
msgstr ""
-#: forum/forms.py:210
-#: forum/skins/default/templates/authopenid/settings.html:21
+#: forum/forms.py:209 templates/authopenid/settings.html:21
msgid "Profile"
msgstr ""
-#: forum/forms.py:241 forum/forms.py:242
+#: forum/forms.py:240 forum/forms.py:241
msgid "this email has already been registered, please use another one"
msgstr ""
-#: forum/forms.py:248
+#: forum/forms.py:247
msgid "Choose email tag filter"
msgstr ""
-#: forum/forms.py:263 forum/forms.py:264
+#: forum/forms.py:262 forum/forms.py:263
msgid "weekly"
msgstr ""
-#: forum/forms.py:263 forum/forms.py:264
+#: forum/forms.py:262 forum/forms.py:263
msgid "no email"
msgstr ""
-#: forum/forms.py:264
+#: forum/forms.py:263
msgid "daily"
msgstr ""
-#: forum/forms.py:279
+#: forum/forms.py:278
msgid "Asked by me"
msgstr ""
-#: forum/forms.py:282
+#: forum/forms.py:281
msgid "Answered by me"
msgstr ""
-#: forum/forms.py:285
+#: forum/forms.py:284
msgid "Individually selected"
msgstr ""
-#: forum/forms.py:288
+#: forum/forms.py:287
msgid "Entire forum (tag filtered)"
msgstr ""
-#: forum/forms.py:342
+#: forum/forms.py:341
msgid "okay, let's try!"
msgstr ""
-#: forum/forms.py:343
+#: forum/forms.py:342
msgid "no OSQA community email please, thanks"
msgstr ""
-#: forum/forms.py:346
+#: forum/forms.py:345
msgid "please choose one of the options above"
msgstr ""
@@ -631,201 +624,286 @@ msgstr ""
msgid "ignored"
msgstr ""
-#: forum/models.py:541 forum/skins/default/templates/badges.html:53
+#: forum/models.py:541 templates/badges.html:53
msgid "gold"
msgstr ""
-#: forum/models.py:542 forum/skins/default/templates/badges.html:61
+#: forum/models.py:542 templates/badges.html:61
msgid "silver"
msgstr ""
-#: forum/models.py:543 forum/skins/default/templates/badges.html:68
+#: forum/models.py:543 templates/badges.html:68
msgid "bronze"
msgstr ""
-#: forum/urls.py:27
+#: forum/urls.py:26
msgid "upfiles/"
msgstr ""
-#: forum/urls.py:31
+#: forum/urls.py:30
msgid "about/"
msgstr ""
-#: forum/urls.py:32
+#: forum/urls.py:31
msgid "faq/"
msgstr ""
-#: forum/urls.py:33
+#: forum/urls.py:32
msgid "privacy/"
msgstr ""
-#: forum/urls.py:34
+#: forum/urls.py:33
msgid "logout/"
msgstr ""
-#: forum/urls.py:35 forum/urls.py:36 forum/urls.py:37 forum/urls.py:54
+#: forum/urls.py:34 forum/urls.py:35 forum/urls.py:36 forum/urls.py:53
msgid "answers/"
msgstr ""
-#: forum/urls.py:35 forum/urls.py:47 forum/urls.py:50 forum/urls.py:54
+#: forum/urls.py:34 forum/urls.py:46 forum/urls.py:49 forum/urls.py:53
msgid "comments/"
msgstr ""
-#: forum/urls.py:36 forum/urls.py:41 forum/urls.py:76
-#: forum/skins/default/templates/user_info.html:45
+#: forum/urls.py:35 forum/urls.py:40 forum/urls.py:75
+#: templates/user_info.html:45
msgid "edit/"
msgstr ""
-#: forum/urls.py:37 forum/urls.py:46
+#: forum/urls.py:36 forum/urls.py:45
msgid "revisions/"
msgstr ""
-#: forum/urls.py:38 forum/urls.py:39 forum/urls.py:40 forum/urls.py:41
-#: forum/urls.py:42 forum/urls.py:43 forum/urls.py:44 forum/urls.py:45
-#: forum/urls.py:46 forum/urls.py:47 forum/urls.py:50
+#: forum/urls.py:37 forum/urls.py:38 forum/urls.py:39 forum/urls.py:40
+#: forum/urls.py:41 forum/urls.py:42 forum/urls.py:43 forum/urls.py:44
+#: forum/urls.py:45 forum/urls.py:46 forum/urls.py:49
msgid "questions/"
msgstr ""
-#: forum/urls.py:39 forum/urls.py:86
+#: forum/urls.py:38 forum/urls.py:85
msgid "ask/"
msgstr ""
-#: forum/urls.py:40
+#: forum/urls.py:39
msgid "unanswered/"
msgstr ""
-#: forum/urls.py:42
+#: forum/urls.py:41
msgid "close/"
msgstr ""
-#: forum/urls.py:43
+#: forum/urls.py:42
msgid "reopen/"
msgstr ""
-#: forum/urls.py:44
+#: forum/urls.py:43
msgid "answer/"
msgstr ""
-#: forum/urls.py:45
+#: forum/urls.py:44
msgid "vote/"
msgstr ""
-#: forum/urls.py:48
+#: forum/urls.py:47
msgid "command/"
msgstr ""
-#: forum/urls.py:58 forum/views.py:437 forum/views/content.py:431
+#: forum/urls.py:57 forum/views.py:440
msgid "question/"
msgstr ""
-#: forum/urls.py:59 forum/urls.py:60
+#: forum/urls.py:58 forum/urls.py:59
msgid "tags/"
msgstr ""
-#: forum/urls.py:62 forum/urls.py:66
+#: forum/urls.py:61 forum/urls.py:65
msgid "mark-tag/"
msgstr ""
-#: forum/urls.py:62
+#: forum/urls.py:61
msgid "interesting/"
msgstr ""
-#: forum/urls.py:66
+#: forum/urls.py:65
msgid "ignored/"
msgstr ""
-#: forum/urls.py:70
+#: forum/urls.py:69
msgid "unmark-tag/"
msgstr ""
-#: forum/urls.py:74 forum/urls.py:76 forum/urls.py:77
+#: forum/urls.py:73 forum/urls.py:75 forum/urls.py:76
msgid "users/"
msgstr ""
-#: forum/urls.py:75
+#: forum/urls.py:74
msgid "moderate-user/"
msgstr ""
-#: forum/urls.py:78 forum/urls.py:79
+#: forum/urls.py:77 forum/urls.py:78
msgid "badges/"
msgstr ""
-#: forum/urls.py:80
+#: forum/urls.py:79
msgid "messages/"
msgstr ""
-#: forum/urls.py:80
+#: forum/urls.py:79
msgid "markread/"
msgstr ""
-#: forum/urls.py:82
+#: forum/urls.py:81
msgid "nimda/"
msgstr ""
-#: forum/urls.py:84
+#: forum/urls.py:83
msgid "upload/"
msgstr ""
-#: forum/urls.py:85 forum/urls.py:86 forum/urls.py:87
+#: forum/urls.py:84 forum/urls.py:85 forum/urls.py:86
msgid "books/"
msgstr ""
-#: forum/urls.py:88
+#: forum/urls.py:87
msgid "search/"
msgstr ""
-#: forum/urls.py:89
+#: forum/urls.py:88
msgid "feedback/"
msgstr ""
-#: forum/urls.py:90 forum/urls.py:91
+#: forum/urls.py:89 forum/urls.py:90
msgid "account/"
msgstr ""
-#: forum/views.py:141 forum/views/meta.py:33
+#: forum/user.py:16 templates/user_tabs.html:7
+msgid "overview"
+msgstr ""
+
+#: forum/user.py:17
+msgid "user profile"
+msgstr ""
+
+#: forum/user.py:18
+msgid "user profile overview"
+msgstr ""
+
+#: forum/user.py:24 templates/user_tabs.html:9
+msgid "recent activity"
+msgstr ""
+
+#: forum/user.py:25
+msgid "recent user activity"
+msgstr ""
+
+#: forum/user.py:26
+msgid "profile - recent activity"
+msgstr ""
+
+#: forum/user.py:33 templates/user_tabs.html:13
+msgid "responses"
+msgstr ""
+
+#: forum/user.py:34 templates/user_tabs.html:12
+msgid "comments and answers to others questions"
+msgstr ""
+
+#: forum/user.py:35
+msgid "profile - responses"
+msgstr ""
+
+#: forum/user.py:42 templates/user_info.html:22 templates/users.html:26
+msgid "reputation"
+msgstr "karma"
+
+#: forum/user.py:43
+msgid "user reputation in the community"
+msgstr "user karma"
+
+#: forum/user.py:44
+msgid "profile - user reputation"
+msgstr "Profile - User's Karma"
+
+#: forum/user.py:50
+msgid "favorite questions"
+msgstr ""
+
+#: forum/user.py:51
+msgid "users favorite questions"
+msgstr ""
+
+#: forum/user.py:52
+msgid "profile - favorite questions"
+msgstr ""
+
+#: forum/user.py:59 templates/user_tabs.html:20
+msgid "casted votes"
+msgstr "votes"
+
+#: forum/user.py:60 templates/user_tabs.html:20
+msgid "user vote record"
+msgstr ""
+
+#: forum/user.py:61
+msgid "profile - votes"
+msgstr ""
+
+#: forum/user.py:68 templates/user_tabs.html:28
+msgid "email subscriptions"
+msgstr ""
+
+#: forum/user.py:69 templates/user_tabs.html:27
+msgid "email subscription settings"
+msgstr ""
+
+#: forum/user.py:70
+msgid "profile - email subscriptions"
+msgstr ""
+
+#: forum/views.py:141
msgid "Q&A forum feedback"
msgstr ""
-#: forum/views.py:142 forum/views/meta.py:34
+#: forum/views.py:142
msgid "Thanks for the feedback!"
msgstr ""
-#: forum/views.py:150 forum/views/meta.py:42
+#: forum/views.py:150
msgid "We look forward to hearing your feedback! Please, give it next time :)"
msgstr ""
-#: forum/views.py:1095 forum/views/content.py:1327
+#: forum/views.py:1098
#, python-format
msgid "subscription saved, %(email)s needs validation, see %(details_url)s"
msgstr ""
+"Your subscription is saved, but email address %(email)s needs to be "
+"validated, please see <a href='%(details_url)s'>more details here</a>"
-#: forum/views.py:1103 forum/views/content.py:1335
+#: forum/views.py:1106
msgid "email update frequency has been set to daily"
msgstr ""
-#: forum/views.py:1980 forum/views.py:1984 forum/views/users.py:836
-#: forum/views/users.py:840
+#: forum/views.py:1982 forum/views.py:1986
msgid "changes saved"
msgstr ""
-#: forum/views.py:1990 forum/views/users.py:846
+#: forum/views.py:1992
msgid "email updates canceled"
msgstr ""
-#: forum/views.py:2157 forum/views/content.py:713
+#: forum/views.py:2159
msgid "uploading images is limited to users with >60 reputation points"
-msgstr ""
+msgstr "sorry, file uploading requires karma >60"
-#: forum/views.py:2159 forum/views/content.py:715
+#: forum/views.py:2161
msgid "allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"
msgstr ""
-#: forum/views.py:2161 forum/views/content.py:717
+#: forum/views.py:2163
#, python-format
msgid "maximum upload file size is %sK"
msgstr ""
-#: forum/views.py:2163 forum/views/content.py:719
+#: forum/views.py:2165
#, python-format
msgid ""
"Error uploading file. Please contact the site administrator. Thank you. %s"
@@ -833,14 +911,18 @@ msgstr ""
#: forum/management/commands/send_email_alerts.py:156
msgid "email update message subject"
-msgstr ""
+msgstr "news from Q&A forum"
#: forum/management/commands/send_email_alerts.py:158
#, python-format
msgid "%(name)s, this is an update message header for a question"
msgid_plural "%(name)s, this is an update message header for %(num)d questions"
msgstr[0] ""
+"<p>Dear %(name)s,</p></p>The following question has been updated on the Q&A "
+"forum:</p>"
msgstr[1] ""
+"<p>Dear %(name)s,</p><p>The following %(num)d questions have been updated on "
+"the Q&A forum:</p>"
#: forum/management/commands/send_email_alerts.py:169
msgid "new question"
@@ -865,280 +947,289 @@ msgid ""
"go to %(link)s to change frequency of email updates or %(email)s "
"administrator"
msgstr ""
+"<p>Please remember that you can always <a href='%(link)s'>adjust</a> "
+"frequency of the email updates or turn them off entirely.<br/>If you believe "
+"that this message was sent in an error, please email about it the forum "
+"administrator at %(email)s.</p><p>Sincerely,</p><p>Your friendly Q&A forum "
+"server.</p>"
+
+#: forum/templatetags/extra_tags.py:164 forum/templatetags/extra_tags.py:193
+#: templates/header.html:33
+msgid "badges"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:165 forum/templatetags/extra_tags.py:192
+msgid "reputation points"
+msgstr "karma"
+
+#: forum/templatetags/extra_tags.py:252
+msgid "2 days ago"
+msgstr ""
-#: forum/skins/default/templates/404.html:24
+#: forum/templatetags/extra_tags.py:254
+msgid "yesterday"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:256
+#, python-format
+msgid "%(hr)d hour ago"
+msgid_plural "%(hr)d hours ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: forum/templatetags/extra_tags.py:258
+#, python-format
+msgid "%(min)d min ago"
+msgid_plural "%(min)d mins ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: middleware/anon_user.py:33
+#, python-format
+msgid "first time greeting with %(url)s"
+msgstr ""
+
+#: templates/404.html:24
msgid "Sorry, could not find the page you requested."
msgstr ""
-#: forum/skins/default/templates/404.html:26
+#: templates/404.html:26
msgid "This might have happened for the following reasons:"
msgstr ""
-#: forum/skins/default/templates/404.html:28
+#: templates/404.html:28
msgid "this question or answer has been deleted;"
msgstr ""
-#: forum/skins/default/templates/404.html:29
+#: templates/404.html:29
msgid "url has error - please check it;"
msgstr ""
-#: forum/skins/default/templates/404.html:30
+#: templates/404.html:30
msgid ""
"the page you tried to visit is protected or you don't have sufficient "
"points, see"
msgstr ""
-#: forum/skins/default/templates/404.html:31
+#: templates/404.html:31
msgid "if you believe this error 404 should not have occured, please"
msgstr ""
-#: forum/skins/default/templates/404.html:32
+#: templates/404.html:32
msgid "report this problem"
msgstr ""
-#: forum/skins/default/templates/404.html:41
-#: forum/skins/default/templates/500.html:27
+#: templates/404.html:41 templates/500.html:27
msgid "back to previous page"
msgstr ""
-#: forum/skins/default/templates/404.html:42
+#: templates/404.html:42
msgid "see all questions"
msgstr ""
-#: forum/skins/default/templates/404.html:43
+#: templates/404.html:43
msgid "see all tags"
msgstr ""
-#: forum/skins/default/templates/500.html:22
+#: templates/500.html:22
msgid "sorry, system error"
msgstr ""
-#: forum/skins/default/templates/500.html:24
+#: templates/500.html:24
msgid "system error log is recorded, error will be fixed as soon as possible"
msgstr ""
-#: forum/skins/default/templates/500.html:25
+#: templates/500.html:25
msgid "please report the error to the site administrators if you wish"
msgstr ""
-#: forum/skins/default/templates/500.html:28
+#: templates/500.html:28
msgid "see latest questions"
msgstr ""
-#: forum/skins/default/templates/500.html:29
+#: templates/500.html:29
msgid "see tags"
msgstr ""
-#: forum/skins/default/templates/about.html:6
-#: forum/skins/default/templates/about.html:11
+#: templates/about.html:6 templates/about.html.py:11
msgid "About"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:5
-#: forum/skins/default/templates/answer_edit.html:48
+#: templates/answer_edit.html:5 templates/answer_edit.html.py:48
msgid "Edit answer"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:25
-#: forum/skins/default/templates/answer_edit.html:28
-#: forum/skins/default/templates/ask.html:26
-#: forum/skins/default/templates/ask.html:29
-#: forum/skins/default/templates/question.html:46
-#: forum/skins/default/templates/question.html:49
-#: forum/skins/default/templates/question.html:565
-#: forum/skins/default/templates/question.html:568
-#: forum/skins/default/templates/question_edit.html:25
-#: forum/skins/default/templates/question_edit.html:28
+#: templates/answer_edit.html:25 templates/answer_edit.html.py:28
+#: templates/ask.html:26 templates/ask.html.py:29 templates/question.html:45
+#: templates/question.html.py:48 templates/question_edit.html:25
+#: templates/question_edit.html.py:28
msgid "hide preview"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:28
-#: forum/skins/default/templates/ask.html:29
-#: forum/skins/default/templates/question.html:49
-#: forum/skins/default/templates/question.html:568
-#: forum/skins/default/templates/question_edit.html:28
+#: templates/answer_edit.html:28 templates/ask.html:29
+#: templates/question.html:48 templates/question_edit.html:28
msgid "show preview"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:48
-#: forum/skins/default/templates/question_edit.html:66
-#: forum/skins/default/templates/question_retag.html:53
-#: forum/skins/default/templates/revisions_answer.html:38
-#: forum/skins/default/templates/revisions_question.html:38
+#: templates/answer_edit.html:48 templates/question_edit.html:66
+#: templates/question_retag.html:53 templates/revisions_answer.html:38
+#: templates/revisions_question.html:38
msgid "back"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:53
-#: forum/skins/default/templates/question_edit.html:71
-#: forum/skins/default/templates/revisions_answer.html:52
-#: forum/skins/default/templates/revisions_question.html:52
+#: templates/answer_edit.html:53 templates/question_edit.html:71
+#: templates/revisions_answer.html:52 templates/revisions_question.html:52
msgid "revision"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:56
-#: forum/skins/default/templates/question_edit.html:75
+#: templates/answer_edit.html:56 templates/question_edit.html:75
msgid "select revision"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:63
-#: forum/skins/default/templates/ask.html:97
-#: forum/skins/default/templates/question.html:443
-#: forum/skins/default/templates/question.html:952
-#: forum/skins/default/templates/question_edit.html:92
+#: templates/answer_edit.html:63 templates/ask.html:97
+#: templates/question.html:434 templates/question_edit.html:92
msgid "Toggle the real time Markdown editor preview"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:63
-#: forum/skins/default/templates/ask.html:97
-#: forum/skins/default/templates/question.html:444
-#: forum/skins/default/templates/question.html:953
-#: forum/skins/default/templates/question_edit.html:92
+#: templates/answer_edit.html:63 templates/ask.html:97
+#: templates/question.html:435 templates/question_edit.html:92
msgid "toggle preview"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:72
-#: forum/skins/default/templates/question_edit.html:124
-#: forum/skins/default/templates/question_retag.html:74
+#: templates/answer_edit.html:72 templates/question_edit.html:118
+#: templates/question_retag.html:74
msgid "Save edit"
msgstr ""
-#: forum/skins/default/templates/answer_edit.html:73
-#: forum/skins/default/templates/close.html:29
-#: forum/skins/default/templates/feedback.html:50
-#: forum/skins/default/templates/question_edit.html:125
-#: forum/skins/default/templates/question_retag.html:75
-#: forum/skins/default/templates/reopen.html:30
-#: forum/skins/default/templates/user_edit.html:87
-#: forum/skins/default/templates/authopenid/changeemail.html:40
+#: templates/answer_edit.html:73 templates/close.html:29
+#: templates/feedback.html:50 templates/question_edit.html:119
+#: templates/question_retag.html:75 templates/reopen.html:30
+#: templates/user_edit.html:87 templates/authopenid/changeemail.html:40
msgid "Cancel"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:4
+#: templates/answer_edit_tips.html:4
msgid "answer tips"
-msgstr ""
+msgstr "Tips"
-#: forum/skins/default/templates/answer_edit_tips.html:7
+#: templates/answer_edit_tips.html:7
msgid "please make your answer relevant to this community"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:10
+#: templates/answer_edit_tips.html:10
msgid "try to give an answer, rather than engage into a discussion"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:13
+#: templates/answer_edit_tips.html:13
msgid "please try to provide details"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:16
-#: forum/skins/default/templates/question_edit_tips.html:13
+#: templates/answer_edit_tips.html:16 templates/question_edit_tips.html:13
msgid "be clear and concise"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:20
-#: forum/skins/default/templates/question_edit_tips.html:17
+#: templates/answer_edit_tips.html:20 templates/question_edit_tips.html:17
msgid "see frequently asked questions"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:26
-#: forum/skins/default/templates/question_edit_tips.html:23
+#: templates/answer_edit_tips.html:26 templates/question_edit_tips.html:23
msgid "Markdown tips"
-msgstr ""
+msgstr "Markdown basics"
-#: forum/skins/default/templates/answer_edit_tips.html:29
-#: forum/skins/default/templates/question_edit_tips.html:26
+#: templates/answer_edit_tips.html:29 templates/question_edit_tips.html:26
msgid "*italic* or __italic__"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:32
-#: forum/skins/default/templates/question_edit_tips.html:29
+#: templates/answer_edit_tips.html:32 templates/question_edit_tips.html:29
msgid "**bold** or __bold__"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:35
-#: forum/skins/default/templates/question_edit_tips.html:32
+#: templates/answer_edit_tips.html:35 templates/question_edit_tips.html:32
msgid "link"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:35
-#: forum/skins/default/templates/answer_edit_tips.html:39
-#: forum/skins/default/templates/question_edit_tips.html:32
-#: forum/skins/default/templates/question_edit_tips.html:37
+#: templates/answer_edit_tips.html:35 templates/answer_edit_tips.html.py:39
+#: templates/question_edit_tips.html:32 templates/question_edit_tips.html:37
msgid "text"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:39
-#: forum/skins/default/templates/question_edit_tips.html:37
+#: templates/answer_edit_tips.html:39 templates/question_edit_tips.html:37
msgid "image"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:43
-#: forum/skins/default/templates/question_edit_tips.html:41
+#: templates/answer_edit_tips.html:43 templates/question_edit_tips.html:41
msgid "numbered list:"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:48
-#: forum/skins/default/templates/question_edit_tips.html:46
+#: templates/answer_edit_tips.html:48 templates/question_edit_tips.html:46
msgid "basic HTML tags are also supported"
msgstr ""
-#: forum/skins/default/templates/answer_edit_tips.html:52
-#: forum/skins/default/templates/question_edit_tips.html:50
+#: templates/answer_edit_tips.html:52 templates/question_edit_tips.html:50
msgid "learn more about Markdown"
msgstr ""
-#: forum/skins/default/templates/ask.html:5
-#: forum/skins/default/templates/ask.html:61
+#: templates/ask.html:5 templates/ask.html.py:61
msgid "Ask a question"
msgstr ""
-#: forum/skins/default/templates/ask.html:68
+#: templates/ask.html:68
msgid "login to post question info"
msgstr ""
+"<span class=\"strong big\">You are welcome to start submitting your question "
+"anonymously</span>. When you submit the post, you will be redirected to the "
+"login/signup page. Your question will be saved in the current session and "
+"will be published after you log in. Login/signup process is very simple. "
+"Login takes about 30 seconds, initial signup takes a minute or less."
-#: forum/skins/default/templates/ask.html:74
+#: templates/ask.html:74
#, python-format
msgid ""
"must have valid %(email)s to post, \n"
" see %(email_validation_faq_url)s\n"
" "
msgstr ""
+"<span class='strong big'>Looks like your email address, %(email)s has not "
+"yet been validated.</span> To post messages you must verify your email, "
+"please see <a href='%(email_validation_faq_url)s'>more details here</a>."
+"<br>You can submit your question now and validate email after that. Your "
+"question will saved as pending meanwhile. "
-#: forum/skins/default/templates/ask.html:112
-#: forum/skins/default/templates/ask.html:119
-#: forum/skins/default/templates/question_edit.html:120
+#: templates/ask.html:112
msgid "(required)"
msgstr ""
-#: forum/skins/default/templates/ask.html:126
+#: templates/ask.html:119
msgid "Login/signup to post your question"
-msgstr ""
+msgstr "Login/Signup to Post"
-#: forum/skins/default/templates/ask.html:128
+#: templates/ask.html:121
msgid "Ask your question"
-msgstr ""
+msgstr "Ask Your Question"
-#: forum/skins/default/templates/badge.html:6
-#: forum/skins/default/templates/badge.html:17
+#: templates/badge.html:6 templates/badge.html.py:17
msgid "Badge"
msgstr ""
-#: forum/skins/default/templates/badge.html:26
+#: templates/badge.html:26
msgid "The users have been awarded with badges:"
msgstr ""
-#: forum/skins/default/templates/badges.html:6
+#: templates/badges.html:6
msgid "Badges summary"
msgstr ""
-#: forum/skins/default/templates/badges.html:17
+#: templates/badges.html:17
msgid "Badges"
msgstr ""
-#: forum/skins/default/templates/badges.html:21
+#: templates/badges.html:21
msgid "Community gives you awards for your questions, answers and votes."
msgstr ""
+"If your questions and answers are highly voted, your contribution to this "
+"Q&amp;A community will be recognized with the variety of badges."
-#: forum/skins/default/templates/badges.html:22
+#: templates/badges.html:22
#, python-format
msgid ""
"Below is the list of available badges and number \n"
@@ -1146,228 +1237,232 @@ msgid ""
"(feedback_faq_url)s.\n"
" "
msgstr ""
+"Currently badges differ only by their level: <strong>gold</strong>, "
+"<strong>silver</strong> and <strong>bronze</strong> (their meanings are "
+"described on the right). In the future there will be many types of badges at "
+"each level. <strong>Please give us your <a href='%(feedback_faq_url)"
+"s'>feedback</a></strong> - what kinds of badges would you like to see and "
+"suggest the activity for which those badges might be awarded."
-#: forum/skins/default/templates/badges.html:50
+#: templates/badges.html:50
msgid "Community badges"
-msgstr ""
+msgstr "Badge levels"
-#: forum/skins/default/templates/badges.html:56
+#: templates/badges.html:56
msgid "gold badge description"
msgstr ""
+"Gold badge is the highest award in this community. To obtain it have to show "
+"profound knowledge and ability in addition to your active participation."
-#: forum/skins/default/templates/badges.html:64
+#: templates/badges.html:64
msgid "silver badge description"
msgstr ""
+"Obtaining silver badge requires significant patience. If you have received "
+"one, that means you have greatly contributed to this community."
-#: forum/skins/default/templates/badges.html:67
+#: templates/badges.html:67
msgid "bronze badge: often given as a special honor"
msgstr ""
-#: forum/skins/default/templates/badges.html:71
+#: templates/badges.html:71
msgid "bronze badge description"
msgstr ""
+"If you are an active participant in this community, you will be recognized "
+"with this badge."
-#: forum/skins/default/templates/book.html:7
+#: templates/book.html:7
msgid "reading channel"
msgstr ""
-#: forum/skins/default/templates/book.html:26
+#: templates/book.html:26
msgid "[author]"
msgstr ""
-#: forum/skins/default/templates/book.html:30
+#: templates/book.html:30
msgid "[publisher]"
msgstr ""
-#: forum/skins/default/templates/book.html:34
+#: templates/book.html:34
msgid "[publication date]"
msgstr ""
-#: forum/skins/default/templates/book.html:38
+#: templates/book.html:38
msgid "[price]"
msgstr ""
-#: forum/skins/default/templates/book.html:39
+#: templates/book.html:39
msgid "currency unit"
msgstr ""
-#: forum/skins/default/templates/book.html:42
+#: templates/book.html:42
msgid "[pages]"
msgstr ""
-#: forum/skins/default/templates/book.html:43
+#: templates/book.html:43
msgid "pages abbreviation"
msgstr ""
-#: forum/skins/default/templates/book.html:46
+#: templates/book.html:46
msgid "[tags]"
msgstr ""
-#: forum/skins/default/templates/book.html:56
+#: templates/book.html:56
msgid "author blog"
msgstr ""
-#: forum/skins/default/templates/book.html:62
+#: templates/book.html:62
msgid "book directory"
msgstr ""
-#: forum/skins/default/templates/book.html:66
+#: templates/book.html:66
msgid "buy online"
msgstr ""
-#: forum/skins/default/templates/book.html:79
+#: templates/book.html:79
msgid "reader questions"
msgstr ""
-#: forum/skins/default/templates/book.html:82
+#: templates/book.html:82
msgid "ask the author"
msgstr ""
-#: forum/skins/default/templates/book.html:88
-#: forum/skins/default/templates/book.html:93
-#: forum/skins/default/templates/users_questions.html:18
+#: templates/book.html:88 templates/book.html.py:93
+#: templates/users_questions.html:18
msgid "this question was selected as favorite"
msgstr ""
-#: forum/skins/default/templates/book.html:88
-#: forum/skins/default/templates/book.html:93
-#: forum/skins/default/templates/users_questions.html:11
-#: forum/skins/default/templates/users_questions.html:18
+#: templates/book.html:88 templates/book.html.py:93
+#: templates/users_questions.html:11 templates/users_questions.html.py:18
msgid "number of times"
msgstr ""
-#: forum/skins/default/templates/book.html:105
-#: forum/skins/default/templates/index.html:50
-#: forum/skins/default/templates/question_summary_list_roll.html:14
-#: forum/skins/default/templates/questions.html:85
-#: forum/skins/default/templates/questions.html:354
-#: forum/skins/default/templates/users_questions.html:32
-#: templates/unanswered.html:39
+#: templates/book.html:105 templates/index.html:49
+#: templates/question_summary_list_roll.html:14 templates/questions.html:80
+#: templates/users_questions.html:32
msgid "votes"
msgstr ""
-#: forum/skins/default/templates/book.html:108
+#: templates/book.html:108
msgid "the answer has been accepted to be correct"
msgstr ""
-#: forum/skins/default/templates/book.html:115
-#: forum/skins/default/templates/index.html:51
-#: forum/skins/default/templates/question_summary_list_roll.html:15
-#: forum/skins/default/templates/questions.html:86
-#: forum/skins/default/templates/questions.html:355
-#: forum/skins/default/templates/users_questions.html:40
-#: templates/unanswered.html:40
+#: templates/book.html:115 templates/index.html:50
+#: templates/question_summary_list_roll.html:15 templates/questions.html:81
+#: templates/users_questions.html:40
msgid "views"
msgstr ""
-#: forum/skins/default/templates/book.html:125
-#: forum/skins/default/templates/index.html:106
-#: forum/skins/default/templates/question.html:489
-#: forum/skins/default/templates/question.html:998
-#: forum/skins/default/templates/question_summary_list_roll.html:52
-#: forum/skins/default/templates/questions.html:141
-#: forum/skins/default/templates/questions.html:258
-#: forum/skins/default/templates/questions.html:410
-#: forum/skins/default/templates/tags.html:49
-#: forum/skins/default/templates/users_questions.html:52
-#: templates/unanswered.html:95 templates/unanswered.html.py:122
+#: templates/book.html:125 templates/index.html:105
+#: templates/question.html:480 templates/question_summary_list_roll.html:52
+#: templates/questions.html:136 templates/tags.html:49
+#: templates/users_questions.html:52
msgid "using tags"
msgstr ""
-#: forum/skins/default/templates/book.html:147
+#: templates/book.html:147
msgid "subscribe to book RSS feed"
msgstr ""
-#: forum/skins/default/templates/book.html:147
-#: forum/skins/default/templates/index.html:157
+#: templates/book.html:147 templates/index.html:156
msgid "subscribe to the questions feed"
msgstr ""
-#: forum/skins/default/templates/close.html:6
-#: forum/skins/default/templates/close.html:16
+#: templates/close.html:6 templates/close.html.py:16
msgid "Close question"
msgstr ""
-#: forum/skins/default/templates/close.html:19
+#: templates/close.html:19
msgid "Close the question"
msgstr ""
-#: forum/skins/default/templates/close.html:25
+#: templates/close.html:25
msgid "Reasons"
msgstr ""
-#: forum/skins/default/templates/close.html:28
+#: templates/close.html:28
msgid "OK to close"
msgstr ""
-#: forum/skins/default/templates/faq.html:11
+#: templates/faq.html:11
msgid "Frequently Asked Questions "
msgstr ""
-#: forum/skins/default/templates/faq.html:16
+#: templates/faq.html:16
msgid "What kinds of questions can I ask here?"
msgstr ""
-#: forum/skins/default/templates/faq.html:17
+#: templates/faq.html:17
msgid ""
"Most importanly - questions should be <strong>relevant</strong> to this "
"community."
msgstr ""
-#: forum/skins/default/templates/faq.html:18
+#: templates/faq.html:18
msgid ""
"Before asking the question - please make sure to use search to see whether "
"your question has alredy been answered."
msgstr ""
+"Before you ask - please make sure to search for a similar question. You can "
+"search questions by their title or tags."
-#: forum/skins/default/templates/faq.html:21
+#: templates/faq.html:21
msgid "What questions should I avoid asking?"
-msgstr ""
+msgstr "What kinds of questions should be avoided?"
-#: forum/skins/default/templates/faq.html:22
+#: templates/faq.html:22
msgid ""
"Please avoid asking questions that are not relevant to this community, too "
"subjective and argumentative."
msgstr ""
-#: forum/skins/default/templates/faq.html:27
+#: templates/faq.html:27
msgid "What should I avoid in my answers?"
msgstr ""
-#: forum/skins/default/templates/faq.html:28
+#: templates/faq.html:28
msgid ""
"is a Q&A site, not a discussion group. Therefore - please avoid having "
"discussions in your answers, comment facility allows some space for brief "
"discussions."
msgstr ""
+"is a <strong>question and answer</strong> site - <strong>it is not a "
+"discussion group</strong>. Please avoid holding debates in your answers as "
+"they tend to dilute the essense of questions and answers. For the brief "
+"discussions please use commenting facility."
-#: forum/skins/default/templates/faq.html:32
+#: templates/faq.html:32
msgid "Who moderates this community?"
msgstr ""
-#: forum/skins/default/templates/faq.html:33
+#: templates/faq.html:33
msgid "The short answer is: <strong>you</strong>."
msgstr ""
-#: forum/skins/default/templates/faq.html:34
+#: templates/faq.html:34
msgid "This website is moderated by the users."
msgstr ""
-#: forum/skins/default/templates/faq.html:35
+#: templates/faq.html:35
msgid ""
"The reputation system allows users earn the authorization to perform a "
"variety of moderation tasks."
msgstr ""
+"Karma system allows users to earn rights to perform a variety of moderation "
+"tasks"
-#: forum/skins/default/templates/faq.html:40
+#: templates/faq.html:40
msgid "How does reputation system work?"
-msgstr ""
+msgstr "How does karma system work?"
-#: forum/skins/default/templates/faq.html:41
+#: templates/faq.html:41
msgid "Rep system summary"
msgstr ""
+"When a question or answer is upvoted, the user who posted them will gain "
+"some points, which are called \"karma points\". These points serve as a "
+"rough measure of the community trust to him/her. Various moderation tasks "
+"are gradually assigned to the users based on those points."
-#: forum/skins/default/templates/faq.html:42
+#: templates/faq.html:42
msgid ""
"For example, if you ask an interesting question or give a helpful answer, "
"your input will be upvoted. On the other hand if the answer is misleading - "
@@ -1378,134 +1473,150 @@ msgid ""
"type of moderation task."
msgstr ""
-#: forum/skins/default/templates/faq.html:53
-#: forum/skins/default/templates/user_votes.html:15
+#: templates/faq.html:53 templates/user_votes.html:15
msgid "upvote"
msgstr ""
-#: forum/skins/default/templates/faq.html:57
+#: templates/faq.html:57
msgid "use tags"
msgstr ""
-#: forum/skins/default/templates/faq.html:62
+#: templates/faq.html:62
msgid "add comments"
msgstr ""
-#: forum/skins/default/templates/faq.html:66
-#: forum/skins/default/templates/user_votes.html:17
+#: templates/faq.html:66 templates/user_votes.html:17
msgid "downvote"
msgstr ""
-#: forum/skins/default/templates/faq.html:69
+#: templates/faq.html:69
msgid "open and close own questions"
msgstr ""
-#: forum/skins/default/templates/faq.html:73
+#: templates/faq.html:73
msgid "retag questions"
msgstr ""
-#: forum/skins/default/templates/faq.html:78
+#: templates/faq.html:78
msgid "edit community wiki questions"
msgstr ""
-#: forum/skins/default/templates/faq.html:83
+#: templates/faq.html:83
msgid "edit any answer"
msgstr ""
-#: forum/skins/default/templates/faq.html:87
+#: templates/faq.html:87
msgid "open any closed question"
msgstr ""
-#: forum/skins/default/templates/faq.html:91
+#: templates/faq.html:91
msgid "delete any comment"
msgstr ""
-#: forum/skins/default/templates/faq.html:95
+#: templates/faq.html:95
msgid "delete any questions and answers and perform other moderation tasks"
msgstr ""
-#: forum/skins/default/templates/faq.html:102
+#: templates/faq.html:102
msgid "how to validate email title"
-msgstr ""
+msgstr "How to validate email and why?"
-#: forum/skins/default/templates/faq.html:104
+#: templates/faq.html:104
#, python-format
msgid ""
"how to validate email info with %(send_email_key_url)s %(gravatar_faq_url)s"
msgstr ""
-
-#: forum/skins/default/templates/faq.html:108
+"<form style='margin:0;padding:0;' action='%(send_email_key_url)s'><p><span "
+"class=\"bigger strong\">How?</span> If you have just set or changed your "
+"email address - <strong>check your email and click the included link</"
+"strong>.<br>The link contains a key generated specifically for you. You can "
+"also <button style='display:inline' type='submit'><strong>get a new key</"
+"strong></button> and check your email again.</p></form><span class=\"bigger "
+"strong\">Why?</span> Email validation is required to make sure that "
+"<strong>only you can post messages</strong> on your behalf and to "
+"<strong>minimize spam</strong> posts.<br>With email you can "
+"<strong>subscribe for updates</strong> on the most interesting questions. "
+"Also, when you sign up for the first time - create a unique <a href='%"
+"(gravatar_faq_url)s'><strong>gravatar</strong></a> personal image.</p>"
+
+#: templates/faq.html:108
msgid "what is gravatar"
-msgstr ""
+msgstr "What is gravatar?"
-#: forum/skins/default/templates/faq.html:109
+#: templates/faq.html:109
msgid "gravatar faq info"
msgstr ""
+"<strong>Gravatar</strong> means <strong>g</strong>lobally <strong>r</"
+"strong>ecognized <strong>avatar</strong> - your unique avatar image "
+"associated with your email address. It's simply a picture that shows next to "
+"your posts on the websites that support gravatar protocol. By default gravar "
+"appears as a square filled with a snowflake-like figure. You can <strong>set "
+"your image</strong> at <a href='http://gravatar.com'><strong>gravatar.com</"
+"strong></a>"
-#: forum/skins/default/templates/faq.html:112
+#: templates/faq.html:112
msgid "To register, do I need to create new password?"
msgstr ""
-#: forum/skins/default/templates/faq.html:113
+#: templates/faq.html:113
msgid ""
"No, you don't have to. You can login through any service that supports "
"OpenID, e.g. Google, Yahoo, AOL, etc."
msgstr ""
-#: forum/skins/default/templates/faq.html:114
+#: templates/faq.html:114
msgid "Login now!"
msgstr ""
-#: forum/skins/default/templates/faq.html:119
+#: templates/faq.html:119
msgid "Why other people can edit my questions/answers?"
msgstr ""
-#: forum/skins/default/templates/faq.html:120
+#: templates/faq.html:120
msgid "Goal of this site is..."
msgstr ""
-#: forum/skins/default/templates/faq.html:120
+#: templates/faq.html:120
msgid ""
"So questions and answers can be edited like wiki pages by experienced users "
"of this site and this improves the overall quality of the knowledge base "
"content."
msgstr ""
-#: forum/skins/default/templates/faq.html:121
+#: templates/faq.html:121
msgid "If this approach is not for you, we respect your choice."
msgstr ""
-#: forum/skins/default/templates/faq.html:125
+#: templates/faq.html:125
msgid "Still have questions?"
msgstr ""
-#: forum/skins/default/templates/faq.html:126
+#: templates/faq.html:126
#, python-format
msgid ""
"Please ask your question at %(ask_question_url)s, help make our community "
"better!"
msgstr ""
+"Please <a href='%(ask_question_url)s'>ask</a> your question, help make our "
+"community better!"
-#: forum/skins/default/templates/faq.html:128
-#: forum/skins/default/templates/header.html:27
-#: forum/skins/default/templates/header.html:55
+#: templates/faq.html:128 templates/header.html:27 templates/header.html.py:55
msgid "questions"
msgstr ""
-#: forum/skins/default/templates/faq.html:128
-#: forum/skins/default/templates/index.html:162
+#: templates/faq.html:128 templates/index.html:161
msgid "."
msgstr ""
-#: forum/skins/default/templates/feedback.html:6
+#: templates/feedback.html:6
msgid "Feedback"
msgstr ""
-#: forum/skins/default/templates/feedback.html:11
+#: templates/feedback.html:11
msgid "Give us your feedback!"
msgstr ""
-#: forum/skins/default/templates/feedback.html:17
+#: templates/feedback.html:17
#, python-format
msgid ""
"\n"
@@ -1515,7 +1626,7 @@ msgid ""
" "
msgstr ""
-#: forum/skins/default/templates/feedback.html:24
+#: templates/feedback.html:24
msgid ""
"\n"
" <span class='big strong'>Dear visitor</span>, we look forward to "
@@ -1524,284 +1635,246 @@ msgid ""
" "
msgstr ""
-#: forum/skins/default/templates/feedback.html:41
+#: templates/feedback.html:41
msgid "(this field is required)"
msgstr ""
-#: forum/skins/default/templates/feedback.html:49
+#: templates/feedback.html:49
msgid "Send Feedback"
msgstr ""
-#: forum/skins/default/templates/footer.html:8
-#: forum/skins/default/templates/header.html:13
-#: forum/skins/default/templates/index.html:120
+#: templates/feedback_email.txt:3
+#, python-format
+msgid ""
+"\n"
+"Hello, this is a %(site_title)s forum feedback message\n"
+msgstr ""
+
+#: templates/feedback_email.txt:9
+msgid "Sender is"
+msgstr ""
+
+#: templates/feedback_email.txt:11 templates/feedback_email.txt.py:14
+msgid "email"
+msgstr ""
+
+#: templates/feedback_email.txt:13
+msgid "anonymous"
+msgstr ""
+
+#: templates/feedback_email.txt:19
+msgid "Message body:"
+msgstr ""
+
+#: templates/footer.html:8 templates/header.html:13 templates/index.html:119
msgid "about"
msgstr ""
-#: forum/skins/default/templates/footer.html:9
-#: forum/skins/default/templates/header.html:14
-#: forum/skins/default/templates/index.html:121
-#: forum/skins/default/templates/question_edit_tips.html:17
+#: templates/footer.html:9 templates/header.html:14 templates/index.html:120
+#: templates/question_edit_tips.html:17
msgid "faq"
msgstr ""
-#: forum/skins/default/templates/footer.html:10
+#: templates/footer.html:10
msgid "privacy policy"
msgstr ""
-#: forum/skins/default/templates/footer.html:19
+#: templates/footer.html:19
msgid "give feedback"
msgstr ""
-#: forum/skins/default/templates/header.html:9
+#: templates/header.html:9
msgid "logout"
msgstr ""
-#: forum/skins/default/templates/header.html:11
+#: templates/header.html:11
msgid "login"
msgstr ""
-#: forum/skins/default/templates/header.html:21
+#: templates/header.html:21
msgid "back to home page"
msgstr ""
-#: forum/skins/default/templates/header.html:29
-#: forum/skins/default/templates/header.html:57
+#: templates/header.html:29 templates/header.html.py:57
msgid "users"
msgstr ""
-#: forum/skins/default/templates/header.html:31
+#: templates/header.html:31
msgid "books"
msgstr ""
-#: forum/skins/default/templates/header.html:33
-#: forum/templatetags/extra_tags.py:165 forum/templatetags/extra_tags.py:194
-msgid "badges"
-msgstr ""
-
-#: forum/skins/default/templates/header.html:34
+#: templates/header.html:34
msgid "unanswered questions"
-msgstr ""
+msgstr "unanswered"
-#: forum/skins/default/templates/header.html:36
+#: templates/header.html:36
msgid "ask a question"
msgstr ""
-#: forum/skins/default/templates/header.html:51
+#: templates/header.html:51
msgid "search"
msgstr ""
-#: forum/skins/default/templates/index.html:8
+#: templates/index.html:8
msgid "Home"
msgstr ""
-#: forum/skins/default/templates/index.html:25
-#: forum/skins/default/templates/questions.html:9
-#: forum/skins/default/templates/questions.html:282
+#: templates/index.html:25 templates/questions.html:8
msgid "Questions"
msgstr ""
-#: forum/skins/default/templates/index.html:27
+#: templates/index.html:27
msgid "last updated questions"
msgstr ""
-#: forum/skins/default/templates/index.html:27
-#: forum/skins/default/templates/questions.html:52
-#: forum/skins/default/templates/questions.html:321
-#: templates/unanswered.html:21
+#: templates/index.html:27 templates/questions.html:47
msgid "newest"
msgstr ""
-#: forum/skins/default/templates/index.html:28
-#: forum/skins/default/templates/questions.html:53
-#: forum/skins/default/templates/questions.html:322
-msgid "most recently updated questions"
-msgstr ""
-
-#: forum/skins/default/templates/index.html:28
-#: forum/skins/default/templates/questions.html:53
-#: forum/skins/default/templates/questions.html:322
-msgid "active"
-msgstr ""
-
-#: forum/skins/default/templates/index.html:29
-#: forum/skins/default/templates/questions.html:54
-#: forum/skins/default/templates/questions.html:323
+#: templates/index.html:28 templates/questions.html:49
msgid "hottest questions"
msgstr ""
-#: forum/skins/default/templates/index.html:29
-#: forum/skins/default/templates/questions.html:54
-#: forum/skins/default/templates/questions.html:323
+#: templates/index.html:28 templates/questions.html:49
msgid "hottest"
msgstr ""
-#: forum/skins/default/templates/index.html:30
-#: forum/skins/default/templates/questions.html:55
-#: forum/skins/default/templates/questions.html:324
+#: templates/index.html:29 templates/questions.html:50
msgid "most voted questions"
msgstr ""
-#: forum/skins/default/templates/index.html:30
-#: forum/skins/default/templates/questions.html:55
-#: forum/skins/default/templates/questions.html:324
+#: templates/index.html:29 templates/questions.html:50
msgid "most voted"
msgstr ""
-#: forum/skins/default/templates/index.html:31
+#: templates/index.html:30
msgid "all questions"
msgstr ""
-#: forum/skins/default/templates/index.html:49
-#: forum/skins/default/templates/question_summary_list_roll.html:13
-#: forum/skins/default/templates/questions.html:84
-#: forum/skins/default/templates/questions.html:353
-#: forum/skins/default/templates/users_questions.html:36
-#: templates/unanswered.html:38
+#: templates/index.html:48 templates/question_summary_list_roll.html:13
+#: templates/questions.html:79 templates/users_questions.html:36
msgid "answers"
msgstr ""
-#: forum/skins/default/templates/index.html:81
-#: forum/skins/default/templates/index.html:95
-#: forum/skins/default/templates/questions.html:116
-#: forum/skins/default/templates/questions.html:130
-#: forum/skins/default/templates/questions.html:385
-#: forum/skins/default/templates/questions.html:399
-#: templates/unanswered.html:70 templates/unanswered.html.py:84
+#: templates/index.html:80 templates/index.html.py:94
+#: templates/questions.html:111 templates/questions.html.py:125
msgid "Posted:"
msgstr ""
-#: forum/skins/default/templates/index.html:84
-#: forum/skins/default/templates/index.html:89
-#: forum/skins/default/templates/questions.html:119
-#: forum/skins/default/templates/questions.html:124
-#: forum/skins/default/templates/questions.html:388
-#: forum/skins/default/templates/questions.html:393
-#: templates/unanswered.html:73 templates/unanswered.html.py:78
+#: templates/index.html:83 templates/index.html.py:88
+#: templates/questions.html:114 templates/questions.html.py:119
msgid "Updated:"
msgstr ""
-#: forum/skins/default/templates/index.html:106
-#: forum/skins/default/templates/question.html:489
-#: forum/skins/default/templates/question.html:998
-#: forum/skins/default/templates/question_summary_list_roll.html:52
-#: forum/skins/default/templates/questions.html:141
-#: forum/skins/default/templates/questions.html:258
-#: forum/skins/default/templates/questions.html:410
-#: forum/skins/default/templates/tags.html:49
-#: forum/skins/default/templates/users_questions.html:52
-#: templates/unanswered.html:95 templates/unanswered.html.py:122
+#: templates/index.html:105 templates/question.html:480
+#: templates/question_summary_list_roll.html:52 templates/questions.html:136
+#: templates/tags.html:49 templates/users_questions.html:52
msgid "see questions tagged"
msgstr ""
-#: forum/skins/default/templates/index.html:117
+#: templates/index.html:116
msgid "welcome to website"
-msgstr ""
+msgstr "Welcome to Q&amp;A forum"
-#: forum/skins/default/templates/index.html:128
+#: templates/index.html:127
msgid "Recent tags"
msgstr ""
-#: forum/skins/default/templates/index.html:133
-#: forum/skins/default/templates/question.html:136
-#: forum/skins/default/templates/question.html:653
+#: templates/index.html:132 templates/question.html:135
#, python-format
msgid "see questions tagged '%(tagname)s'"
msgstr ""
-#: forum/skins/default/templates/index.html:136
-#: forum/skins/default/templates/index.html:162
+#: templates/index.html:135 templates/index.html.py:161
msgid "popular tags"
-msgstr ""
+msgstr "tags"
-#: forum/skins/default/templates/index.html:141
+#: templates/index.html:140
msgid "Recent awards"
-msgstr ""
+msgstr "Recent badges"
-#: forum/skins/default/templates/index.html:147
+#: templates/index.html:146
msgid "given to"
msgstr ""
-#: forum/skins/default/templates/index.html:152
+#: templates/index.html:151
msgid "all awards"
-msgstr ""
+msgstr "all badges"
-#: forum/skins/default/templates/index.html:157
+#: templates/index.html:156
msgid "subscribe to last 30 questions by RSS"
msgstr ""
-#: forum/skins/default/templates/index.html:162
+#: templates/index.html:161
msgid "Still looking for more? See"
msgstr ""
-#: forum/skins/default/templates/index.html:162
+#: templates/index.html:161
msgid "complete list of questions"
-msgstr ""
+msgstr "list of all questions"
-#: forum/skins/default/templates/index.html:162
-#: forum/skins/default/templates/authopenid/signup.html:28
+#: templates/index.html:161 templates/authopenid/signup.html:26
msgid "or"
msgstr ""
-#: forum/skins/default/templates/index.html:162
+#: templates/index.html:161
msgid "Please help us answer"
msgstr ""
-#: forum/skins/default/templates/index.html:162
+#: templates/index.html:161
msgid "list of unanswered questions"
-msgstr ""
+msgstr "unanswered questions"
-#: forum/skins/default/templates/logout.html:6
-#: forum/skins/default/templates/logout.html:16
+#: templates/logout.html:6 templates/logout.html.py:16
msgid "Logout"
msgstr ""
-#: forum/skins/default/templates/logout.html:19
+#: templates/logout.html:19
msgid ""
"As a registered user you can login with your OpenID, log out of the site or "
"permanently remove your account."
msgstr ""
+"Clicking <strong>Logout</strong> will log you out from the forumbut will not "
+"sign you off from your OpenID provider.</p><p>If you wish to sign off "
+"completely - please make sure to log out from your OpenID provider as well."
-#: forum/skins/default/templates/logout.html:20
+#: templates/logout.html:20
msgid "Logout now"
-msgstr ""
+msgstr "Logout Now"
-#: forum/skins/default/templates/notarobot.html:3
+#: templates/notarobot.html:3
msgid "Please prove that you are a Human Being"
msgstr ""
-#: forum/skins/default/templates/notarobot.html:10
+#: templates/notarobot.html:10
msgid "I am a Human Being"
msgstr ""
-#: forum/skins/default/templates/pagesize.html:6
+#: templates/pagesize.html:6
msgid "posts per page"
msgstr ""
-#: forum/skins/default/templates/paginator.html:6
-#: forum/skins/default/templates/paginator.html:7
+#: templates/paginator.html:6 templates/paginator.html.py:7
msgid "previous"
msgstr ""
-#: forum/skins/default/templates/paginator.html:19
+#: templates/paginator.html:19
msgid "current page"
msgstr ""
-#: forum/skins/default/templates/paginator.html:22
-#: forum/skins/default/templates/paginator.html:29
+#: templates/paginator.html:22 templates/paginator.html.py:29
msgid "page number "
msgstr ""
-#: forum/skins/default/templates/paginator.html:22
-#: forum/skins/default/templates/paginator.html:29
+#: templates/paginator.html:22 templates/paginator.html.py:29
msgid "number - make blank in english"
msgstr ""
-#: forum/skins/default/templates/paginator.html:33
+#: templates/paginator.html:33
msgid "next page"
msgstr ""
-#: forum/skins/default/templates/post_contributor_info.html:9
+#: templates/post_contributor_info.html:9
#, python-format
msgid ""
"\n"
@@ -1814,170 +1887,141 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/post_contributor_info.html:19
+#: templates/post_contributor_info.html:19
msgid "asked"
msgstr ""
-#: forum/skins/default/templates/post_contributor_info.html:22
+#: templates/post_contributor_info.html:22
msgid "answered"
msgstr ""
-#: forum/skins/default/templates/post_contributor_info.html:24
+#: templates/post_contributor_info.html:24
msgid "posted"
msgstr ""
-#: forum/skins/default/templates/post_contributor_info.html:45
+#: templates/post_contributor_info.html:45
msgid "updated"
msgstr ""
-#: forum/skins/default/templates/privacy.html:6
-#: forum/skins/default/templates/privacy.html:11
+#: templates/privacy.html:6 templates/privacy.html.py:11
msgid "Privacy policy"
msgstr ""
-#: forum/skins/default/templates/privacy.html:15
+#: templates/privacy.html:15
msgid "general message about privacy"
msgstr ""
+"Respecting users privacy is an important core principle of this Q&amp;A "
+"forum. Information on this page details how this forum protects your "
+"privacy, and what type of information is collected."
-#: forum/skins/default/templates/privacy.html:18
+#: templates/privacy.html:18
msgid "Site Visitors"
msgstr ""
-#: forum/skins/default/templates/privacy.html:20
+#: templates/privacy.html:20
msgid "what technical information is collected about visitors"
msgstr ""
+"Information on question views, revisions of questions and answers - both "
+"times and content are recorded for each user in order to correctly count "
+"number of views, maintain data integrity and report relevant updates."
-#: forum/skins/default/templates/privacy.html:23
+#: templates/privacy.html:23
msgid "Personal Information"
msgstr ""
-#: forum/skins/default/templates/privacy.html:25
+#: templates/privacy.html:25
msgid "details on personal information policies"
msgstr ""
+"Members of this community may choose to display personally identifiable "
+"information in their profiles. Forum will never display such information "
+"without a request from the user."
-#: forum/skins/default/templates/privacy.html:28
+#: templates/privacy.html:28
msgid "Other Services"
msgstr ""
-#: forum/skins/default/templates/privacy.html:30
+#: templates/privacy.html:30
msgid "details on sharing data with third parties"
msgstr ""
+"None of the data that is not openly shown on the forum by the choice of the "
+"user is shared with any third party."
-#: forum/skins/default/templates/privacy.html:35
+#: templates/privacy.html:35
msgid "cookie policy details"
msgstr ""
+"Forum software relies on the internet cookie technology to keep track of "
+"user sessions. Cookies must be enabled in your browser so that forum can "
+"work for you."
-#: forum/skins/default/templates/privacy.html:37
+#: templates/privacy.html:37
msgid "Policy Changes"
msgstr ""
-#: forum/skins/default/templates/privacy.html:38
+#: templates/privacy.html:38
msgid "how privacy policies can be changed"
msgstr ""
+"These policies may be adjusted to improve protection of user's privacy. "
+"Whenever such changes occur, users will be notified via the internal "
+"messaging system. "
-#: forum/skins/default/templates/question.html:78
-#: forum/skins/default/templates/question.html:79
-#: forum/skins/default/templates/question.html:95
-#: forum/skins/default/templates/question.html:97
-#: forum/skins/default/templates/question.html:597
-#: forum/skins/default/templates/question.html:598
-#: forum/skins/default/templates/question.html:614
-#: forum/skins/default/templates/question.html:616
+#: templates/question.html:77 templates/question.html.py:78
+#: templates/question.html:94 templates/question.html.py:96
msgid "i like this post (click again to cancel)"
msgstr ""
-#: forum/skins/default/templates/question.html:81
-#: forum/skins/default/templates/question.html:99
-#: forum/skins/default/templates/question.html:258
-#: forum/skins/default/templates/question.html:600
-#: forum/skins/default/templates/question.html:618
-#: forum/skins/default/templates/question.html:775
+#: templates/question.html:80 templates/question.html.py:98
+#: templates/question.html:257
msgid "current number of votes"
msgstr ""
-#: forum/skins/default/templates/question.html:90
-#: forum/skins/default/templates/question.html:91
-#: forum/skins/default/templates/question.html:104
-#: forum/skins/default/templates/question.html:105
-#: forum/skins/default/templates/question.html:609
-#: forum/skins/default/templates/question.html:610
-#: forum/skins/default/templates/question.html:623
-#: forum/skins/default/templates/question.html:624
+#: templates/question.html:89 templates/question.html.py:90
+#: templates/question.html:103 templates/question.html.py:104
msgid "i dont like this post (click again to cancel)"
msgstr ""
-#: forum/skins/default/templates/question.html:110
-#: forum/skins/default/templates/question.html:111
-#: forum/skins/default/templates/question.html:628
-#: forum/skins/default/templates/question.html:629
+#: templates/question.html:109 templates/question.html.py:110
msgid "mark this question as favorite (click again to cancel)"
msgstr ""
-#: forum/skins/default/templates/question.html:117
-#: forum/skins/default/templates/question.html:118
-#: forum/skins/default/templates/question.html:635
-#: forum/skins/default/templates/question.html:636
+#: templates/question.html:116 templates/question.html.py:117
msgid "remove favorite mark from this question (click again to restore mark)"
msgstr ""
-#: forum/skins/default/templates/question.html:141
-#: forum/skins/default/templates/question.html:295
-#: forum/skins/default/templates/question.html:658
-#: forum/skins/default/templates/question.html:812
-#: forum/skins/default/templates/revisions_answer.html:58
-#: forum/skins/default/templates/revisions_question.html:58
+#: templates/question.html:140 templates/question.html.py:294
+#: templates/revisions_answer.html:58 templates/revisions_question.html:58
msgid "edit"
msgstr ""
-#: forum/skins/default/templates/question.html:146
-#: forum/skins/default/templates/question.html:663
+#: templates/question.html:145
msgid "reopen"
msgstr ""
-#: forum/skins/default/templates/question.html:150
-#: forum/skins/default/templates/question.html:667
+#: templates/question.html:149
msgid "close"
msgstr ""
-#: forum/skins/default/templates/question.html:156
-#: forum/skins/default/templates/question.html:301
-#: forum/skins/default/templates/question.html:673
-#: forum/skins/default/templates/question.html:817
+#: templates/question.html:155 templates/question.html.py:299
msgid ""
"report as offensive (i.e containing spam, advertising, malicious text, etc.)"
msgstr ""
-#: forum/skins/default/templates/question.html:157
-#: forum/skins/default/templates/question.html:302
-#: forum/skins/default/templates/question.html:674
-#: forum/skins/default/templates/question.html:818
+#: templates/question.html:156 templates/question.html.py:300
msgid "flag offensive"
msgstr ""
-#: forum/skins/default/templates/question.html:165
-#: forum/skins/default/templates/question.html:313
-#: forum/skins/default/templates/question.html:682
-#: forum/skins/default/templates/question.html:829
+#: templates/question.html:164 templates/question.html.py:311
msgid "delete"
msgstr ""
-#: forum/skins/default/templates/question.html:183
-#: forum/skins/default/templates/question.html:333
-#: forum/skins/default/templates/question.html:700
-#: forum/skins/default/templates/question.html:849
+#: templates/question.html:182 templates/question.html.py:331
msgid "delete this comment"
msgstr ""
-#: forum/skins/default/templates/question.html:194
-#: forum/skins/default/templates/question.html:344
-#: forum/skins/default/templates/question.html:368
-#: forum/skins/default/templates/question.html:711
-#: forum/skins/default/templates/question.html:860
+#: templates/question.html:193 templates/question.html.py:342
msgid "add comment"
-msgstr ""
+msgstr "post a comment"
-#: forum/skins/default/templates/question.html:198
-#: forum/skins/default/templates/question.html:715
+#: templates/question.html:197
#, python-format
msgid ""
"\n"
@@ -1991,8 +2035,7 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/question.html:204
-#: forum/skins/default/templates/question.html:721
+#: templates/question.html:203
#, python-format
msgid ""
"\n"
@@ -2007,21 +2050,18 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/question.html:220
-#: forum/skins/default/templates/question.html:737
+#: templates/question.html:219
#, python-format
msgid ""
"The question has been closed for the following reason \"%(close_reason)s\" by"
msgstr ""
-#: forum/skins/default/templates/question.html:222
-#: forum/skins/default/templates/question.html:739
+#: templates/question.html:221
#, python-format
msgid "close date %(closed_at)s"
msgstr ""
-#: forum/skins/default/templates/question.html:230
-#: forum/skins/default/templates/question.html:747
+#: templates/question.html:229
#, python-format
msgid ""
"\n"
@@ -2034,81 +2074,59 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/question.html:238
-#: forum/skins/default/templates/question.html:755
+#: templates/question.html:237
msgid "oldest answers will be shown first"
msgstr ""
-#: forum/skins/default/templates/question.html:238
-#: forum/skins/default/templates/question.html:755
+#: templates/question.html:237
msgid "oldest answers"
-msgstr ""
+msgstr "oldest"
-#: forum/skins/default/templates/question.html:240
-#: forum/skins/default/templates/question.html:757
+#: templates/question.html:239
msgid "newest answers will be shown first"
msgstr ""
-#: forum/skins/default/templates/question.html:240
-#: forum/skins/default/templates/question.html:757
+#: templates/question.html:239
msgid "newest answers"
-msgstr ""
+msgstr "newest"
-#: forum/skins/default/templates/question.html:242
-#: forum/skins/default/templates/question.html:759
+#: templates/question.html:241
msgid "most voted answers will be shown first"
msgstr ""
-#: forum/skins/default/templates/question.html:242
-#: forum/skins/default/templates/question.html:759
+#: templates/question.html:241
msgid "popular answers"
-msgstr ""
+msgstr "most voted"
-#: forum/skins/default/templates/question.html:256
-#: forum/skins/default/templates/question.html:257
-#: forum/skins/default/templates/question.html:773
-#: forum/skins/default/templates/question.html:774
+#: templates/question.html:255 templates/question.html.py:256
msgid "i like this answer (click again to cancel)"
msgstr ""
-#: forum/skins/default/templates/question.html:263
-#: forum/skins/default/templates/question.html:264
-#: forum/skins/default/templates/question.html:780
-#: forum/skins/default/templates/question.html:781
+#: templates/question.html:262 templates/question.html.py:263
msgid "i dont like this answer (click again to cancel)"
msgstr ""
-#: forum/skins/default/templates/question.html:269
-#: forum/skins/default/templates/question.html:270
-#: forum/skins/default/templates/question.html:786
-#: forum/skins/default/templates/question.html:787
+#: templates/question.html:268 templates/question.html.py:269
msgid "mark this answer as favorite (click again to undo)"
msgstr ""
-#: forum/skins/default/templates/question.html:275
-#: forum/skins/default/templates/question.html:276
-#: forum/skins/default/templates/question.html:792
-#: forum/skins/default/templates/question.html:793
+#: templates/question.html:274 templates/question.html.py:275
msgid "the author of the question has selected this answer as correct"
msgstr ""
-#: forum/skins/default/templates/question.html:289
-#: forum/skins/default/templates/question.html:806
+#: templates/question.html:288
msgid "answer permanent link"
msgstr ""
-#: forum/skins/default/templates/question.html:290
-#: forum/skins/default/templates/question.html:807
+#: templates/question.html:289
msgid "permanent link"
-msgstr ""
+msgstr "link"
-#: forum/skins/default/templates/question.html:313
-#: forum/skins/default/templates/question.html:829
+#: templates/question.html:311
msgid "undelete"
msgstr ""
-#: forum/skins/default/templates/question.html:348
-#: forum/skins/default/templates/question.html:864
+#: templates/question.html:346
#, python-format
msgid ""
"\n"
@@ -2123,8 +2141,7 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/question.html:354
-#: forum/skins/default/templates/question.html:870
+#: templates/question.html:352
#, python-format
msgid ""
"\n"
@@ -2139,24 +2156,18 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/question.html:367
-msgid "comments"
-msgstr ""
-
-#: forum/skins/default/templates/question.html:387
-#: forum/skins/default/templates/question.html:390
-#: forum/skins/default/templates/question.html:896
-#: forum/skins/default/templates/question.html:899
+#: templates/question.html:378 templates/question.html.py:381
msgid "Notify me once a day when there are any new answers"
msgstr ""
+"<strong>Notify me</strong> once a day by email when there are any new "
+"answers or updates"
-#: forum/skins/default/templates/question.html:393
-#: forum/skins/default/templates/question.html:902
+#: templates/question.html:384
msgid "Notify me weekly when there are any new answers"
msgstr ""
+"<strong>Notify me</strong> weekly when there are any new answers or updates"
-#: forum/skins/default/templates/question.html:398
-#: forum/skins/default/templates/question.html:907
+#: templates/question.html:389
#, python-format
msgid ""
"\n"
@@ -2164,215 +2175,170 @@ msgid ""
"(profile_url)s\n"
" "
msgstr ""
+"\n"
+"(note: you can always <a href='%(profile_url)s?"
+"sort=email_subscriptions'>adjust frequency</a> of email updates)"
-#: forum/skins/default/templates/question.html:405
-#: forum/skins/default/templates/question.html:914
+#: templates/question.html:396
msgid "once you sign in you will be able to subscribe for any updates here"
msgstr ""
+"<span class='strong'>Here</span> (once you log in) you will be able to sign "
+"up for the periodic email updates about this question."
-#: forum/skins/default/templates/question.html:416
-#: forum/skins/default/templates/question.html:925
+#: templates/question.html:407
msgid "Your answer"
msgstr ""
-#: forum/skins/default/templates/question.html:418
-#: forum/skins/default/templates/question.html:927
+#: templates/question.html:409
msgid "Be the first one to answer this question!"
msgstr ""
-#: forum/skins/default/templates/question.html:424
-#: forum/skins/default/templates/question.html:933
+#: templates/question.html:415
msgid "you can answer anonymously and then login"
msgstr ""
+"<span class='strong big'>Please start posting your answer anonymously</span> "
+"- your answer will be saved within the current session and published after "
+"you log in or create a new account. Please try to give a <strong>substantial "
+"answer</strong>, for discussions, <strong>please use comments</strong> and "
+"<strong>please do remember to vote</strong> (after you log in)!"
-#: forum/skins/default/templates/question.html:428
-#: forum/skins/default/templates/question.html:937
+#: templates/question.html:419
msgid "answer your own question only to give an answer"
msgstr ""
+"<span class='big strong'>You are welcome to answer your own question</span>, "
+"but please make sure to give an <strong>answer</strong>. Remember that you "
+"can always <strong>revise your original question</strong>. Please "
+"<strong>use comments for discussions</strong> and <strong>please don't "
+"forget to vote :)</strong> for the answers that you liked (or perhaps did "
+"not like)! "
-#: forum/skins/default/templates/question.html:430
-#: forum/skins/default/templates/question.html:939
+#: templates/question.html:421
msgid "please only give an answer, no discussions"
msgstr ""
+"<span class='big strong'>Please try to give a substantial answer</span>. If "
+"you wanted to comment on the question or answer, just <strong>use the "
+"commenting tool</strong>. Please remember that you can always <strong>revise "
+"your answers</strong> - no need to answer the same question twice. Also, "
+"please <strong>don't forget to vote</strong> - it really helps to select the "
+"best questions and answers!"
-#: forum/skins/default/templates/question.html:466
-#: forum/skins/default/templates/question.html:975
+#: templates/question.html:457
msgid "Login/Signup to Post Your Answer"
msgstr ""
-#: forum/skins/default/templates/question.html:469
-#: forum/skins/default/templates/question.html:978
+#: templates/question.html:460
msgid "Answer Your Own Question"
msgstr ""
-#: forum/skins/default/templates/question.html:471
-#: forum/skins/default/templates/question.html:980
+#: templates/question.html:462
msgid "Answer the question"
-msgstr ""
+msgstr "Post Your Answer"
-#: forum/skins/default/templates/question.html:484
-#: forum/skins/default/templates/question.html:993
+#: templates/question.html:475
msgid "Question tags"
-msgstr ""
+msgstr "Tags"
-#: forum/skins/default/templates/question.html:494
-#: forum/skins/default/templates/question.html:1003
+#: templates/question.html:485
msgid "question asked"
-msgstr ""
+msgstr "Asked"
-#: forum/skins/default/templates/question.html:497
-#: forum/skins/default/templates/question.html:1006
+#: templates/question.html:488
msgid "question was seen"
-msgstr ""
+msgstr "Seen"
-#: forum/skins/default/templates/question.html:497
-#: forum/skins/default/templates/question.html:1006
+#: templates/question.html:488
msgid "times"
msgstr ""
-#: forum/skins/default/templates/question.html:500
-#: forum/skins/default/templates/question.html:1009
+#: templates/question.html:491
msgid "last updated"
-msgstr ""
+msgstr "Last updated"
-#: forum/skins/default/templates/question.html:505
-#: forum/skins/default/templates/question.html:1014
+#: templates/question.html:496
msgid "Related questions"
msgstr ""
-#: forum/skins/default/templates/question_edit.html:5
-#: forum/skins/default/templates/question_edit.html:66
+#: templates/question_edit.html:5 templates/question_edit.html.py:66
msgid "Edit question"
msgstr ""
-#: forum/skins/default/templates/question_edit_tips.html:4
+#: templates/question_edit_tips.html:4
msgid "question tips"
-msgstr ""
+msgstr "Tips"
-#: forum/skins/default/templates/question_edit_tips.html:7
+#: templates/question_edit_tips.html:7
msgid "please ask a relevant question"
-msgstr ""
+msgstr "ask a question relevant to the CNPROG community"
-#: forum/skins/default/templates/question_edit_tips.html:10
+#: templates/question_edit_tips.html:10
msgid "please try provide enough details"
-msgstr ""
+msgstr "provide enough details"
-#: forum/skins/default/templates/question_retag.html:4
-#: forum/skins/default/templates/question_retag.html:53
+#: templates/question_retag.html:4 templates/question_retag.html.py:53
msgid "Change tags"
msgstr ""
-#: forum/skins/default/templates/question_retag.html:40
+#: templates/question_retag.html:40
msgid "up to 5 tags, less than 20 characters each"
msgstr ""
-#: forum/skins/default/templates/question_retag.html:83
+#: templates/question_retag.html:83
msgid "Why use and modify tags?"
msgstr ""
-#: forum/skins/default/templates/question_retag.html:86
+#: templates/question_retag.html:86
msgid "tags help us keep Questions organized"
msgstr ""
-#: forum/skins/default/templates/question_retag.html:94
+#: templates/question_retag.html:94
msgid "tag editors receive special awards from the community"
msgstr ""
-#: forum/skins/default/templates/questions.html:29
-#: forum/skins/default/templates/questions.html:33
-#: forum/skins/default/templates/questions.html:303
+#: templates/questions.html:29
msgid "Found by tags"
+msgstr "Tagged questions"
+
+#: templates/questions.html:33
+msgid "Search results"
msgstr ""
-#: forum/skins/default/templates/questions.html:29
-#: forum/skins/default/templates/questions.html:39
-#: forum/skins/default/templates/questions.html:309
+#: templates/questions.html:35
msgid "Found by title"
msgstr ""
-#: forum/skins/default/templates/questions.html:29
-#: forum/skins/default/templates/questions.html:45
-#: forum/skins/default/templates/questions.html:315
-msgid "All questions"
+#: templates/questions.html:39
+msgid "Unanswered questions"
msgstr ""
-#: forum/skins/default/templates/questions.html:37
-#: forum/skins/default/templates/questions.html:307
-msgid "Search results"
+#: templates/questions.html:41
+msgid "All questions"
msgstr ""
-#: forum/skins/default/templates/questions.html:43
-#: forum/skins/default/templates/questions.html:313
-#: templates/unanswered.html:8 templates/unanswered.html.py:19
-msgid "Unanswered questions"
+#: templates/questions.html:47
+msgid "most recently asked questions"
msgstr ""
-#: forum/skins/default/templates/questions.html:52
-#: forum/skins/default/templates/questions.html:321
-#: templates/unanswered.html:21
-msgid "most recently asked questions"
+#: templates/questions.html:48
+msgid "most recently updated questions"
msgstr ""
-#: forum/skins/default/templates/questions.html:144
-msgid "Category: "
+#: templates/questions.html:48
+msgid "active"
msgstr ""
-#: forum/skins/default/templates/questions.html:150
-#: forum/skins/default/templates/questions.html:418
+#: templates/questions.html:144
msgid "Did not find anything?"
msgstr ""
-#: forum/skins/default/templates/questions.html:153
-#: forum/skins/default/templates/questions.html:421
+#: templates/questions.html:147
msgid "Did not find what you were looking for?"
msgstr ""
-#: forum/skins/default/templates/questions.html:155
-#: forum/skins/default/templates/questions.html:423
+#: templates/questions.html:149
msgid "Please, post your question!"
msgstr ""
-#: forum/skins/default/templates/questions.html:170
-#, python-format
-msgid ""
-"\n"
-"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
-"\t\t\t"
-msgid_plural ""
-"\n"
-"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
-"\t\t\t"
-msgstr[0] ""
-msgstr[1] ""
-
-#: forum/skins/default/templates/questions.html:177
-#, python-format
-msgid ""
-"\n"
-"\t\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
-"\t\t\t\t"
-msgid_plural ""
-"\n"
-"\t\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
-"\t\t\t\t"
-msgstr[0] ""
-msgstr[1] ""
-
-#: forum/skins/default/templates/questions.html:183
-#, python-format
-msgid ""
-"\n"
-"\t\t\t\thave total %(q_num)s questions\n"
-"\t\t\t\t"
-msgid_plural ""
-"\n"
-"\t\t\t\thave total %(q_num)s questions\n"
-"\t\t\t\t"
-msgstr[0] ""
-msgstr[1] ""
-
-#: forum/skins/default/templates/questions.html:192
-#: forum/skins/default/templates/questions.html:437
+#: templates/questions.html:163
#, python-format
msgid ""
"\n"
@@ -2383,10 +2349,15 @@ msgid_plural ""
" have total %(q_num)s questions tagged %(tagname)s\n"
" "
msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question tagged</p><p><span "
+"class=\"tag\">%(tagname)s</span></p>"
msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions tagged</p><div "
+"class=\"tags\"><span class=\"tag\">%(tagname)s</span></div>"
-#: forum/skins/default/templates/questions.html:200
-#: forum/skins/default/templates/questions.html:445
+#: templates/questions.html:171
#, python-format
msgid ""
"\n"
@@ -2399,10 +2370,15 @@ msgid_plural ""
"s in full text\n"
" "
msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question containing "
+"<strong><span class=\"darkred\">%(searchtitle)s</span></strong></p>"
msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions containing "
+"<strong><span class=\"darkred\">%(searchtitle)s</span></strong></p>"
-#: forum/skins/default/templates/questions.html:206
-#: forum/skins/default/templates/questions.html:451
+#: templates/questions.html:177
#, python-format
msgid ""
"\n"
@@ -2415,10 +2391,17 @@ msgid_plural ""
"s\n"
" "
msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question with title "
+"containing <strong><span class=\"darkred\">%(searchtitle)s</span></strong></"
+"p>"
msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions with title "
+"containing <strong><span class=\"darkred\">%(searchtitle)s</span></strong></"
+"p>"
-#: forum/skins/default/templates/questions.html:214
-#: forum/skins/default/templates/questions.html:459
+#: templates/questions.html:185
#, python-format
msgid ""
"\n"
@@ -2429,10 +2412,15 @@ msgid_plural ""
" have total %(q_num)s unanswered questions\n"
" "
msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question without an "
+"accepted answer</p>"
msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions without an "
+"accepted answer</p>"
-#: forum/skins/default/templates/questions.html:220
-#: forum/skins/default/templates/questions.html:465
+#: templates/questions.html:191
#, python-format
msgid ""
"\n"
@@ -2443,250 +2431,236 @@ msgid_plural ""
" have total %(q_num)s questions\n"
" "
msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question</p>"
msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions<p>"
-#: forum/skins/default/templates/questions.html:231
-#: forum/skins/default/templates/questions.html:475
+#: templates/questions.html:201
msgid "latest questions info"
-msgstr ""
+msgstr "<strong>Newest</strong> questions are shown first."
-#: forum/skins/default/templates/questions.html:235
-#: forum/skins/default/templates/questions.html:479
+#: templates/questions.html:205
msgid "Questions are sorted by the <strong>time of last update</strong>."
msgstr ""
-#: forum/skins/default/templates/questions.html:236
-#: forum/skins/default/templates/questions.html:480
+#: templates/questions.html:206
msgid "Most recently answered ones are shown first."
-msgstr ""
+msgstr "<strong>Most recently answered</strong> questions are shown first."
-#: forum/skins/default/templates/questions.html:240
-#: forum/skins/default/templates/questions.html:484
+#: templates/questions.html:210
msgid "Questions sorted by <strong>number of responses</strong>."
-msgstr ""
+msgstr "Questions sorted by the <strong>number of answers</strong>."
-#: forum/skins/default/templates/questions.html:241
-#: forum/skins/default/templates/questions.html:485
+#: templates/questions.html:211
msgid "Most answered questions are shown first."
-msgstr ""
+msgstr " "
-#: forum/skins/default/templates/questions.html:245
-#: forum/skins/default/templates/questions.html:489
+#: templates/questions.html:215
msgid "Questions are sorted by the <strong>number of votes</strong>."
msgstr ""
-#: forum/skins/default/templates/questions.html:246
-#: forum/skins/default/templates/questions.html:490
+#: templates/questions.html:216
msgid "Most voted questions are shown first."
msgstr ""
-#: forum/skins/default/templates/questions.html:254
-#: forum/skins/default/templates/questions.html:498
-#: templates/unanswered.html:118
+#: templates/questions.html:224
msgid "Related tags"
-msgstr ""
+msgstr "Tags"
-#: forum/skins/default/templates/questions.html:264
-#: forum/skins/default/templates/questions.html:501
-#: forum/skins/default/templates/tag_selector.html:10
-#: forum/skins/default/templates/tag_selector.html:27
+#: templates/questions.html:227 templates/tag_selector.html:10
+#: templates/tag_selector.html.py:27
#, python-format
msgid "see questions tagged '%(tag_name)s'"
msgstr ""
-#: forum/skins/default/templates/reopen.html:6
-#: forum/skins/default/templates/reopen.html:16
+#: templates/reopen.html:6 templates/reopen.html.py:16
msgid "Reopen question"
msgstr ""
-#: forum/skins/default/templates/reopen.html:19
+#: templates/reopen.html:19
msgid "Open the previously closed question"
msgstr ""
-#: forum/skins/default/templates/reopen.html:22
+#: templates/reopen.html:22
msgid "The question was closed for the following reason "
msgstr ""
-#: forum/skins/default/templates/reopen.html:22
+#: templates/reopen.html:22
msgid "reason - leave blank in english"
msgstr ""
-#: forum/skins/default/templates/reopen.html:22
+#: templates/reopen.html:22
msgid "on "
msgstr ""
-#: forum/skins/default/templates/reopen.html:22
+#: templates/reopen.html:22
msgid "date closed"
msgstr ""
-#: forum/skins/default/templates/reopen.html:29
+#: templates/reopen.html:29
msgid "Reopen this question"
msgstr ""
-#: forum/skins/default/templates/revisions_answer.html:7
-#: forum/skins/default/templates/revisions_answer.html:38
-#: forum/skins/default/templates/revisions_question.html:8
-#: forum/skins/default/templates/revisions_question.html:38
+#: templates/revisions_answer.html:7 templates/revisions_answer.html.py:38
+#: templates/revisions_question.html:8 templates/revisions_question.html:38
msgid "Revision history"
msgstr ""
-#: forum/skins/default/templates/revisions_answer.html:50
-#: forum/skins/default/templates/revisions_question.html:50
+#: templates/revisions_answer.html:50 templates/revisions_question.html:50
msgid "click to hide/show revision"
msgstr ""
-#: forum/skins/default/templates/tag_selector.html:4
+#: templates/tag_selector.html:4
msgid "Interesting tags"
msgstr ""
-#: forum/skins/default/templates/tag_selector.html:14
+#: templates/tag_selector.html:14
#, python-format
msgid "remove '%(tag_name)s' from the list of interesting tags"
msgstr ""
-#: forum/skins/default/templates/tag_selector.html:20
-#: forum/skins/default/templates/tag_selector.html:37
+#: templates/tag_selector.html:20 templates/tag_selector.html.py:37
msgid "Add"
msgstr ""
-#: forum/skins/default/templates/tag_selector.html:21
+#: templates/tag_selector.html:21
msgid "Ignored tags"
msgstr ""
-#: forum/skins/default/templates/tag_selector.html:31
+#: templates/tag_selector.html:31
#, python-format
msgid "remove '%(tag_name)s' from the list of ignored tags"
msgstr ""
-#: forum/skins/default/templates/tag_selector.html:40
+#: templates/tag_selector.html:40
msgid "keep ingored questions hidden"
msgstr ""
-#: forum/skins/default/templates/tags.html:6
-#: forum/skins/default/templates/tags.html:30
+#: templates/tags.html:6 templates/tags.html.py:30
msgid "Tag list"
msgstr ""
-#: forum/skins/default/templates/tags.html:32
+#: templates/tags.html:32
msgid "sorted alphabetically"
msgstr ""
-#: forum/skins/default/templates/tags.html:32
+#: templates/tags.html:32
msgid "by name"
msgstr ""
-#: forum/skins/default/templates/tags.html:33
+#: templates/tags.html:33
msgid "sorted by frequency of tag use"
msgstr ""
-#: forum/skins/default/templates/tags.html:33
+#: templates/tags.html:33
msgid "by popularity"
msgstr ""
-#: forum/skins/default/templates/tags.html:39
+#: templates/tags.html:39
msgid "All tags matching query"
msgstr ""
-#: forum/skins/default/templates/tags.html:39
+#: templates/tags.html:39
msgid "all tags - make this empty in english"
msgstr ""
-#: forum/skins/default/templates/tags.html:42
+#: templates/tags.html:42
msgid "Nothing found"
msgstr ""
-#: forum/skins/default/templates/user_edit.html:6
+#: templates/user_edit.html:6
msgid "Edit user profile"
msgstr ""
-#: forum/skins/default/templates/user_edit.html:19
+#: templates/user_edit.html:19
msgid "edit profile"
msgstr ""
-#: forum/skins/default/templates/user_edit.html:31
+#: templates/user_edit.html:31
msgid "image associated with your email address"
msgstr ""
-#: forum/skins/default/templates/user_edit.html:31
+#: templates/user_edit.html:31
#, python-format
msgid "avatar, see %(gravatar_faq_url)s"
-msgstr ""
+msgstr "<a href='%(gravatar_faq_url)s'>gravatar</a>"
-#: forum/skins/default/templates/user_edit.html:36
-#: forum/skins/default/templates/user_info.html:56
+#: templates/user_edit.html:36 templates/user_info.html:56
msgid "Registered user"
msgstr ""
-#: forum/skins/default/templates/user_edit.html:43
+#: templates/user_edit.html:43
msgid "Screen Name"
msgstr ""
-#: forum/skins/default/templates/user_edit.html:86
-#: forum/skins/default/templates/user_email_subscriptions.html:23
+#: templates/user_edit.html:86 templates/user_email_subscriptions.html:20
msgid "Update"
msgstr ""
-#: forum/skins/default/templates/user_email_subscriptions.html:8
+#: templates/user_email_subscriptions.html:8
msgid "Email subscription settings"
msgstr ""
-#: forum/skins/default/templates/user_email_subscriptions.html:9
+#: templates/user_email_subscriptions.html:9
msgid "email subscription settings info"
msgstr ""
+"<span class='big strong'>Adjust frequency of email updates.</span> Receive "
+"updates on interesting questions by email, <strong><br/>help the community</"
+"strong> by answering questions of your colleagues. If you do not wish to "
+"receive emails - select 'no email' on all items below.<br/>Updates are only "
+"sent when there is any new activity on selected items."
-#: forum/skins/default/templates/user_email_subscriptions.html:24
+#: templates/user_email_subscriptions.html:21
msgid "Stop sending email"
-msgstr ""
+msgstr "Stop Email"
-#: forum/skins/default/templates/user_info.html:22
-msgid "karma"
-msgstr ""
-
-#: forum/skins/default/templates/user_info.html:32
+#: templates/user_info.html:32
msgid "Moderate this user"
msgstr ""
-#: forum/skins/default/templates/user_info.html:45
+#: templates/user_info.html:45
msgid "update profile"
msgstr ""
-#: forum/skins/default/templates/user_info.html:60
+#: templates/user_info.html:60
msgid "real name"
msgstr ""
-#: forum/skins/default/templates/user_info.html:65
+#: templates/user_info.html:65
msgid "member for"
-msgstr ""
+msgstr "member since"
-#: forum/skins/default/templates/user_info.html:70
+#: templates/user_info.html:70
msgid "last seen"
msgstr ""
-#: forum/skins/default/templates/user_info.html:76
+#: templates/user_info.html:76
msgid "user website"
msgstr ""
-#: forum/skins/default/templates/user_info.html:82
+#: templates/user_info.html:82
msgid "location"
msgstr ""
-#: forum/skins/default/templates/user_info.html:89
+#: templates/user_info.html:89
msgid "age"
msgstr ""
-#: forum/skins/default/templates/user_info.html:90
+#: templates/user_info.html:90
msgid "age unit"
-msgstr ""
+msgstr "years old"
-#: forum/skins/default/templates/user_info.html:96
+#: templates/user_info.html:96
msgid "todays unused votes"
msgstr ""
-#: forum/skins/default/templates/user_info.html:97
+#: templates/user_info.html:97
msgid "votes left"
msgstr ""
-#: forum/skins/default/templates/user_stats.html:12
+#: templates/user_stats.html:12
#, python-format
msgid ""
"\n"
@@ -2699,7 +2673,7 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/user_stats.html:23
+#: templates/user_stats.html:23
#, python-format
msgid ""
"\n"
@@ -2712,16 +2686,16 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/user_stats.html:36
+#: templates/user_stats.html:36
#, python-format
msgid "the answer has been voted for %(vote_count)s times"
msgstr ""
-#: forum/skins/default/templates/user_stats.html:36
+#: templates/user_stats.html:36
msgid "this answer has been selected as correct"
msgstr ""
-#: forum/skins/default/templates/user_stats.html:46
+#: templates/user_stats.html:46
#, python-format
msgid ""
"\n"
@@ -2732,9 +2706,13 @@ msgid_plural ""
" the answer has been commented %(comment_count)s times\n"
" "
msgstr[0] ""
+"\n"
+"(one comment)"
msgstr[1] ""
+"\n"
+"(%(comment_count)s comments)"
-#: forum/skins/default/templates/user_stats.html:61
+#: templates/user_stats.html:61
#, python-format
msgid ""
"\n"
@@ -2747,23 +2725,23 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/user_stats.html:72
+#: templates/user_stats.html:72
msgid "thumb up"
msgstr ""
-#: forum/skins/default/templates/user_stats.html:73
+#: templates/user_stats.html:73
msgid "user has voted up this many times"
msgstr ""
-#: forum/skins/default/templates/user_stats.html:77
+#: templates/user_stats.html:77
msgid "thumb down"
msgstr ""
-#: forum/skins/default/templates/user_stats.html:78
+#: templates/user_stats.html:78
msgid "user voted down this many times"
msgstr ""
-#: forum/skins/default/templates/user_stats.html:87
+#: templates/user_stats.html:87
#, python-format
msgid ""
"\n"
@@ -2776,13 +2754,13 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/user_stats.html:100
+#: templates/user_stats.html:100
#, python-format
msgid ""
"see other questions with %(view_user)s's contributions tagged '%(tag_name)s' "
msgstr ""
-#: forum/skins/default/templates/user_stats.html:115
+#: templates/user_stats.html:115
#, python-format
msgid ""
"\n"
@@ -2795,233 +2773,236 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forum/skins/default/templates/user_tabs.html:7
+#: templates/user_tabs.html:7
msgid "User profile"
msgstr ""
-#: forum/skins/default/templates/user_tabs.html:7 forum/views/users.py:875
-msgid "overview"
-msgstr ""
-
-#: forum/skins/default/templates/user_tabs.html:9 forum/views/users.py:883
-msgid "recent activity"
-msgstr ""
-
-#: forum/skins/default/templates/user_tabs.html:12 forum/views/users.py:893
-msgid "comments and answers to others questions"
-msgstr ""
-
-#: forum/skins/default/templates/user_tabs.html:13 forum/views/users.py:892
-msgid "responses"
-msgstr ""
-
-#: forum/skins/default/templates/user_tabs.html:16
+#: templates/user_tabs.html:16
msgid "graph of user reputation"
-msgstr ""
+msgstr "Graph of user karma"
-#: forum/skins/default/templates/user_tabs.html:17
+#: templates/user_tabs.html:17
msgid "reputation history"
-msgstr ""
-
-#: forum/skins/default/templates/user_tabs.html:20 forum/views/users.py:919
-msgid "user vote record"
-msgstr ""
-
-#: forum/skins/default/templates/user_tabs.html:20 forum/views/users.py:918
-msgid "casted votes"
-msgstr ""
+msgstr "karma history"
-#: forum/skins/default/templates/user_tabs.html:23
+#: templates/user_tabs.html:23
msgid "questions that user selected as his/her favorite"
msgstr ""
-#: forum/skins/default/templates/user_tabs.html:24
+#: templates/user_tabs.html:24
msgid "favorites"
msgstr ""
-#: forum/skins/default/templates/user_tabs.html:27 forum/views/users.py:928
-msgid "email subscription settings"
-msgstr ""
-
-#: forum/skins/default/templates/user_tabs.html:28 forum/views/users.py:927
-msgid "email subscriptions"
-msgstr ""
-
-#: forum/skins/default/templates/users.html:6
-#: forum/skins/default/templates/users.html:24
+#: templates/users.html:6 templates/users.html.py:24
msgid "Users"
msgstr ""
-#: forum/skins/default/templates/users.html:26 forum/views/users.py:901
-msgid "reputation"
-msgstr ""
-
-#: forum/skins/default/templates/users.html:27
+#: templates/users.html:27
msgid "recent"
msgstr ""
-#: forum/skins/default/templates/users.html:28
+#: templates/users.html:28
msgid "oldest"
msgstr ""
-#: forum/skins/default/templates/users.html:29
+#: templates/users.html:29
msgid "by username"
msgstr ""
-#: forum/skins/default/templates/users.html:35
+#: templates/users.html:35
#, python-format
msgid "users matching query %(suser)s:"
msgstr ""
-#: forum/skins/default/templates/users.html:39
+#: templates/users.html:39
msgid "Nothing found."
msgstr ""
-#: forum/skins/default/templates/users_questions.html:11
+#: templates/users_questions.html:11
msgid "this questions was selected as favorite"
msgstr ""
-#: forum/skins/default/templates/users_questions.html:12
+#: templates/users_questions.html:12
msgid "thumb-up on"
msgstr ""
-#: forum/skins/default/templates/users_questions.html:19
+#: templates/users_questions.html:19
msgid "thumb-up off"
msgstr ""
-#: forum/skins/default/templates/users_questions.html:34
+#: templates/users_questions.html:34
msgid "this answer has been accepted to be correct"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeemail.html:3
-#: forum/skins/default/templates/authopenid/changeemail.html:9
-#: forum/skins/default/templates/authopenid/changeemail.html:38
+#: templates/authopenid/changeemail.html:3
+#: templates/authopenid/changeemail.html:9
+#: templates/authopenid/changeemail.html:38
msgid "Change email"
-msgstr ""
+msgstr "Change Email"
-#: forum/skins/default/templates/authopenid/changeemail.html:11
+#: templates/authopenid/changeemail.html:11
msgid "Save your email address"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeemail.html:16
+#: templates/authopenid/changeemail.html:16
#, python-format
msgid "change %(email)s info"
msgstr ""
+"<span class=\"strong big\">Enter your new email into the box below</span> if "
+"you'd like to use another email for <strong>update subscriptions</strong>."
+"<br>Currently you are using <strong>%(email)s</strong>"
-#: forum/skins/default/templates/authopenid/changeemail.html:18
+#: templates/authopenid/changeemail.html:18
#, python-format
msgid "here is why email is required, see %(gravatar_faq_url)s"
msgstr ""
+"<span class='strong big'>Please enter your email address in the box below.</"
+"span> Valid email address is required on this Q&amp;A forum. If you like, "
+"you can <strong>receive updates</strong> on interesting questions or entire "
+"forum via email. Also, your email is used to create a unique <a href='%"
+"(gravatar_faq_url)s'><strong>gravatar</strong></a> image for your account. "
+"Email addresses are never shown or otherwise shared with anybody else."
-#: forum/skins/default/templates/authopenid/changeemail.html:31
+#: templates/authopenid/changeemail.html:31
msgid "Your new Email"
msgstr ""
+"<strong>Your new Email:</strong> (will <strong>not</strong> be shown to "
+"anyone, must be valid)"
-#: forum/skins/default/templates/authopenid/changeemail.html:31
+#: templates/authopenid/changeemail.html:31
msgid "Your Email"
msgstr ""
+"<strong>Your Email</strong> (<i>must be valid, never shown to others</i>)"
-#: forum/skins/default/templates/authopenid/changeemail.html:38
+#: templates/authopenid/changeemail.html:38
msgid "Save Email"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeemail.html:49
+#: templates/authopenid/changeemail.html:49
msgid "Validate email"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeemail.html:52
+#: templates/authopenid/changeemail.html:52
#, python-format
msgid "validate %(email)s info or go to %(change_email_url)s"
msgstr ""
+"<span class=\"strong big\">An email with a validation link has been sent to %"
+"(email)s.</span> Please <strong>follow the emailed link</strong> with your "
+"web browser. Email validation is necessary to help insure the proper use of "
+"email on <span class=\"orange\">Q&amp;A</span>. If you would like to use "
+"<strong>another email</strong>, please <a href='%(change_email_url)"
+"s'><strong>change it again</strong></a>."
-#: forum/skins/default/templates/authopenid/changeemail.html:57
+#: templates/authopenid/changeemail.html:57
msgid "Email not changed"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeemail.html:60
+#: templates/authopenid/changeemail.html:60
#, python-format
msgid "old %(email)s kept, if you like go to %(change_email_url)s"
msgstr ""
+"<span class=\"strong big\">Your email address %(email)s has not been changed."
+"</span> If you decide to change it later - you can always do it by editing "
+"it in your user profile or by using the <a href='%(change_email_url)"
+"s'><strong>previous form</strong></a> again."
-#: forum/skins/default/templates/authopenid/changeemail.html:65
+#: templates/authopenid/changeemail.html:65
msgid "Email changed"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeemail.html:68
+#: templates/authopenid/changeemail.html:68
#, python-format
msgid "your current %(email)s can be used for this"
msgstr ""
+"<span class='big strong'>Your email address is now set to %(email)s.</span> "
+"Updates on the questions that you like most will be sent to this address. "
+"Email notifications are sent once a day or less frequently - only when there "
+"are any news."
-#: forum/skins/default/templates/authopenid/changeemail.html:73
+#: templates/authopenid/changeemail.html:73
msgid "Email verified"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeemail.html:76
+#: templates/authopenid/changeemail.html:76
msgid "thanks for verifying email"
msgstr ""
+"<span class=\"big strong\">Thank you for verifying your email!</span> Now "
+"you can <strong>ask</strong> and <strong>answer</strong> questions. Also if "
+"you find a very interesting question you can <strong>subscribe for the "
+"updates</strong> - then will be notified about changes <strong>once a day</"
+"strong> or less frequently."
-#: forum/skins/default/templates/authopenid/changeemail.html:81
+#: templates/authopenid/changeemail.html:81
msgid "email key not sent"
-msgstr ""
+msgstr "Validation email not sent"
-#: forum/skins/default/templates/authopenid/changeemail.html:84
+#: templates/authopenid/changeemail.html:84
#, python-format
msgid "email key not sent %(email)s change email here %(change_link)s"
msgstr ""
+"<span class='big strong'>Your current email address %(email)s has been "
+"validated before</span> so the new key was not sent. You can <a href='%"
+"(change_link)s'>change</a> email used for update subscriptions if necessary."
-#: forum/skins/default/templates/authopenid/changeopenid.html:4
-#: forum/skins/default/templates/authopenid/changeopenid.html:30
-#: forum/skins/default/templates/authopenid/settings.html:34
+#: templates/authopenid/changeopenid.html:4
+#: templates/authopenid/changeopenid.html:30
+#: templates/authopenid/settings.html:34
msgid "Change OpenID"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeopenid.html:8
+#: templates/authopenid/changeopenid.html:8
msgid "Account: change OpenID URL"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeopenid.html:12
+#: templates/authopenid/changeopenid.html:12
msgid ""
"This is where you can change your OpenID URL. Make sure you remember it!"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeopenid.html:14
-#: forum/skins/default/templates/authopenid/delete.html:14
-#: forum/skins/default/templates/authopenid/delete.html:24
+#: templates/authopenid/changeopenid.html:14
+#: templates/authopenid/delete.html:14 templates/authopenid/delete.html:24
msgid "Please correct errors below:"
msgstr ""
-#: forum/skins/default/templates/authopenid/changeopenid.html:29
+#: templates/authopenid/changeopenid.html:29
msgid "OpenID URL:"
msgstr ""
-#: forum/skins/default/templates/authopenid/changepw.html:5
-#: forum/skins/default/templates/authopenid/changepw.html:14
-#: forum/skins/default/templates/authopenid/settings.html:29
+#: templates/authopenid/changepw.html:5 templates/authopenid/changepw.html:14
+#: templates/authopenid/settings.html:29
msgid "Change password"
msgstr ""
-#: forum/skins/default/templates/authopenid/changepw.html:7
+#: templates/authopenid/changepw.html:7
msgid "Account: change password"
-msgstr ""
+msgstr "Change your password"
-#: forum/skins/default/templates/authopenid/changepw.html:8
+#: templates/authopenid/changepw.html:8
msgid "This is where you can change your password. Make sure you remember it!"
msgstr ""
+"<span class='strong'>To change your password</span> please fill out and "
+"submit this form"
-#: forum/skins/default/templates/authopenid/complete.html:19
+#: templates/authopenid/complete.html:19
msgid "Connect your OpenID with this site"
-msgstr ""
+msgstr "New user signup"
-#: forum/skins/default/templates/authopenid/complete.html:22
+#: templates/authopenid/complete.html:22
msgid "Connect your OpenID with your account on this site"
-msgstr ""
+msgstr "New user signup"
-#: forum/skins/default/templates/authopenid/complete.html:27
+#: templates/authopenid/complete.html:27
#, python-format
msgid "register new %(provider)s account info, see %(gravatar_faq_url)s"
msgstr ""
+"<p><span class=\"big strong\">You are here for the first time with your %"
+"(provider)s login.</span> Please create your <strong>screen name</strong> "
+"and save your <strong>email</strong> address. Saved email address will let "
+"you <strong>subscribe for the updates</strong> on the most interesting "
+"questions and will be used to create and retrieve your unique avatar image - "
+"<a href='%(gravatar_faq_url)s'><strong>gravatar</strong></a>.</p>"
-#: forum/skins/default/templates/authopenid/complete.html:31
+#: templates/authopenid/complete.html:31
#, python-format
msgid ""
"%(username)s already exists, choose another name for \n"
@@ -3029,172 +3010,260 @@ msgid ""
"(gravatar_faq_url)s\n"
" "
msgstr ""
+"<p><span class='strong big'>Oops... looks like screen name %(username)s is "
+"already used in another account.</span></p><p>Please choose another screen "
+"name to use with your %(provider)s login. Also, a valid email address is "
+"required on the <span class='orange'>Q&amp;A</span> forum. Your email is "
+"used to create a unique <a href='%(gravatar_faq_url)s'><strong>gravatar</"
+"strong></a> image for your account. If you like, you can <strong>receive "
+"updates</strong> on the interesting questions or entire forum by email. "
+"Email addresses are never shown or otherwise shared with anybody else.</p>"
-#: forum/skins/default/templates/authopenid/complete.html:35
+#: templates/authopenid/complete.html:35
#, python-format
msgid ""
"register new external %(provider)s account info, see %(gravatar_faq_url)s"
msgstr ""
+"<p><span class=\"big strong\">You are here for the first time with your %"
+"(provider)s login.</span></p><p>You can either keep your <strong>screen "
+"name</strong> the same as your %(provider)s login name or choose some other "
+"nickname.</p><p>Also, please save a valid <strong>email</strong> address. "
+"With the email you can <strong>subscribe for the updates</strong> on the "
+"most interesting questions. Email address is also used to create and "
+"retrieve your unique avatar image - <a href='%(gravatar_faq_url)"
+"s'><strong>gravatar</strong></a>.</p>"
-#: forum/skins/default/templates/authopenid/complete.html:38
+#: templates/authopenid/complete.html:38
#, python-format
msgid "register new Facebook connect account info, see %(gravatar_faq_url)s"
msgstr ""
+"<p><span class=\"big strong\">You are here for the first time with your "
+"Facebook login.</span> Please create your <strong>screen name</strong> "
+"and save your <strong>email</strong> address. Saved email address will let "
+"you <strong>subscribe for the updates</strong> on the most interesting "
+"questions and will be used to create and retrieve your unique avatar image - "
+"<a href='%(gravatar_faq_url)s'><strong>gravatar</strong></a>.</p>"
-#: forum/skins/default/templates/authopenid/complete.html:42
+#: templates/authopenid/complete.html:42
msgid "This account already exists, please use another."
msgstr ""
-#: forum/skins/default/templates/authopenid/complete.html:57
+#: templates/authopenid/complete.html:57
msgid "Sorry, looks like we have some errors:"
msgstr ""
-#: forum/skins/default/templates/authopenid/complete.html:82
+#: templates/authopenid/complete.html:82
msgid "Screen name label"
-msgstr ""
+msgstr "<strong>Screen Name</strong> (<i>will be shown to others</i>)"
-#: forum/skins/default/templates/authopenid/complete.html:89
+#: templates/authopenid/complete.html:89
msgid "Email address label"
msgstr ""
+"<strong>Email Address</strong> (<i>will <strong>not</strong> be shared with "
+"anyone, must be valid</i>)"
-#: forum/skins/default/templates/authopenid/complete.html:95
-#: forum/skins/default/templates/authopenid/complete.html:103
-msgid "Tag filter tool will be your right panel, once you log in."
-msgstr ""
-
-#: forum/skins/default/templates/authopenid/complete.html:96
-#: forum/skins/default/templates/authopenid/signup.html:18
+#: templates/authopenid/complete.html:95 templates/authopenid/signup.html:18
msgid "receive updates motivational blurb"
msgstr ""
+"<strong>Receive forum updates by email</strong> - this will help our "
+"community grow and become more useful.<br/>By default <span "
+"class='orange'>Q&amp;A</span> forum sends up to <strong>one email digest per "
+"week</strong> - only when there is anything new.<br/>If you like, please "
+"adjust this now or any time later from your user account."
-#: forum/skins/default/templates/authopenid/complete.html:100
-#: forum/skins/default/templates/authopenid/signup.html:22
-msgid "please select one of the options above"
+#: templates/authopenid/complete.html:99
+msgid "Tag filter tool will be your right panel, once you log in."
msgstr ""
-#: forum/skins/default/templates/authopenid/complete.html:104
+#: templates/authopenid/complete.html:100
msgid "create account"
-msgstr ""
+msgstr "Signup"
-#: forum/skins/default/templates/authopenid/complete.html:113
+#: templates/authopenid/complete.html:109
msgid "Existing account"
msgstr ""
-#: forum/skins/default/templates/authopenid/complete.html:114
+#: templates/authopenid/complete.html:110
msgid "user name"
msgstr ""
-#: forum/skins/default/templates/authopenid/complete.html:115
+#: templates/authopenid/complete.html:111
msgid "password"
msgstr ""
-#: forum/skins/default/templates/authopenid/complete.html:122
+#: templates/authopenid/complete.html:118
msgid "Register"
msgstr ""
-#: forum/skins/default/templates/authopenid/complete.html:123
-#: forum/skins/default/templates/authopenid/signin.html:151
+#: templates/authopenid/complete.html:119 templates/authopenid/signin.html:151
msgid "Forgot your password?"
msgstr ""
-#: forum/skins/default/templates/authopenid/delete.html:4
-#: forum/skins/default/templates/authopenid/settings.html:38
+#: templates/authopenid/confirm_email.txt:2
+msgid "Thank you for registering at our Q&A forum!"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:4
+msgid "Your account details are:"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:6
+msgid "Username:"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:7
+#: templates/authopenid/delete.html:19
+msgid "Password:"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:9
+msgid "Please sign in here:"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:12
+#: templates/authopenid/email_validation.txt:14
+#: templates/authopenid/sendpw_email.txt:8
+msgid ""
+"Sincerely,\n"
+"Forum Administrator"
+msgstr ""
+"Sincerely,\n"
+"Q&A Forum Administrator"
+
+#: templates/authopenid/delete.html:4 templates/authopenid/settings.html:38
msgid "Delete account"
msgstr ""
-#: forum/skins/default/templates/authopenid/delete.html:8
+#: templates/authopenid/delete.html:8
msgid "Account: delete account"
msgstr ""
-#: forum/skins/default/templates/authopenid/delete.html:12
+#: templates/authopenid/delete.html:12
msgid ""
"Note: After deleting your account, anyone will be able to register this "
"username."
msgstr ""
-#: forum/skins/default/templates/authopenid/delete.html:16
+#: templates/authopenid/delete.html:16
msgid "Check confirm box, if you want delete your account."
msgstr ""
-#: forum/skins/default/templates/authopenid/delete.html:19
-msgid "Password:"
-msgstr ""
-
-#: forum/skins/default/templates/authopenid/delete.html:31
+#: templates/authopenid/delete.html:31
msgid "I am sure I want to delete my account."
msgstr ""
-#: forum/skins/default/templates/authopenid/delete.html:32
+#: templates/authopenid/delete.html:32
msgid "Password/OpenID URL"
msgstr ""
-#: forum/skins/default/templates/authopenid/delete.html:32
+#: templates/authopenid/delete.html:32
msgid "(required for your security)"
msgstr ""
-#: forum/skins/default/templates/authopenid/delete.html:34
+#: templates/authopenid/delete.html:34
msgid "Delete account permanently"
msgstr ""
-#: forum/skins/default/templates/authopenid/external_legacy_login_info.html:4
-#: forum/skins/default/templates/authopenid/external_legacy_login_info.html:7
+#: templates/authopenid/email_validation.txt:2
+msgid "Greetings from the Q&A forum"
+msgstr ""
+
+#: templates/authopenid/email_validation.txt:4
+msgid "To make use of the Forum, please follow the link below:"
+msgstr ""
+
+#: templates/authopenid/email_validation.txt:8
+msgid "Following the link above will help us verify your email address."
+msgstr ""
+
+#: templates/authopenid/email_validation.txt:10
+msgid ""
+"If you beleive that this message was sent in mistake - \n"
+"no further action is needed. Just ingore this email, we apologize\n"
+"for any inconvenience"
+msgstr ""
+
+#: templates/authopenid/external_legacy_login_info.html:4
+#: templates/authopenid/external_legacy_login_info.html:7
msgid "Traditional login information"
msgstr ""
-#: forum/skins/default/templates/authopenid/external_legacy_login_info.html:12
+#: templates/authopenid/external_legacy_login_info.html:12
#, python-format
msgid ""
"how to login with password through external login website or use %"
"(feedback_url)s"
msgstr ""
-#: forum/skins/default/templates/authopenid/sendpw.html:4
-#: forum/skins/default/templates/authopenid/sendpw.html:7
+#: templates/authopenid/sendpw.html:4 templates/authopenid/sendpw.html.py:7
msgid "Send new password"
-msgstr ""
+msgstr "Recover password"
-#: forum/skins/default/templates/authopenid/sendpw.html:10
+#: templates/authopenid/sendpw.html:10
msgid "password recovery information"
msgstr ""
+"<span class='big strong'>Forgot you password? No problems - just get a new "
+"one!</span><br/>Please follow the following steps:<br/>&bull; submit your "
+"user name below and check your email<br/>&bull; <strong>follow the "
+"activation link</strong> for the new password - sent to you by email and "
+"login with the suggested password<br/>&bull; at this you might want to "
+"change your password to something you can remember better"
-#: forum/skins/default/templates/authopenid/sendpw.html:21
+#: templates/authopenid/sendpw.html:21
msgid "Reset password"
-msgstr ""
+msgstr "Send me a new password"
-#: forum/skins/default/templates/authopenid/sendpw.html:22
+#: templates/authopenid/sendpw.html:22
msgid "return to login"
msgstr ""
-#: forum/skins/default/templates/authopenid/settings.html:4
+#: templates/authopenid/sendpw_email.txt:2
+#, python-format
+msgid ""
+"Someone has requested to reset your password on %(site_url)s.\n"
+"If it were not you, it is safe to ignore this email."
+msgstr ""
+
+#: templates/authopenid/sendpw_email.txt:5
+#, python-format
+msgid ""
+"email explanation how to use new %(password)s for %(username)s\n"
+"with the %(key_link)s"
+msgstr ""
+"To change your password, please follow these steps:\n"
+"* visit this link: %(key_link)s\n"
+"* login with user name %(username)s and password %(password)s\n"
+"* go to your user profile and set the password to something you can remember"
+
+#: templates/authopenid/settings.html:4
msgid "Account functions"
msgstr ""
-#: forum/skins/default/templates/authopenid/settings.html:30
+#: templates/authopenid/settings.html:30
msgid "Give your account a new password."
msgstr ""
-#: forum/skins/default/templates/authopenid/settings.html:31
+#: templates/authopenid/settings.html:31
msgid "Change email "
msgstr ""
-#: forum/skins/default/templates/authopenid/settings.html:32
+#: templates/authopenid/settings.html:32
msgid "Add or update the email address associated with your account."
msgstr ""
-#: forum/skins/default/templates/authopenid/settings.html:35
+#: templates/authopenid/settings.html:35
msgid "Change openid associated to your account"
msgstr ""
-#: forum/skins/default/templates/authopenid/settings.html:39
+#: templates/authopenid/settings.html:39
msgid "Erase your username and all your data from website"
msgstr ""
-#: forum/skins/default/templates/authopenid/signin.html:5
-#: forum/skins/default/templates/authopenid/signin.html:21
+#: templates/authopenid/signin.html:5 templates/authopenid/signin.html:21
msgid "User login"
-msgstr ""
+msgstr "User login"
-#: forum/skins/default/templates/authopenid/signin.html:28
+#: templates/authopenid/signin.html:28
#, python-format
msgid ""
"\n"
@@ -3202,200 +3271,149 @@ msgid ""
"log in\n"
" "
msgstr ""
+"\n"
+"<span class=\"strong big\">Your answer to </span> <i>\"<strong>%(title)s</"
+"strong> %(summary)s...\"</i> <span class=\"strong big\">is saved and will be "
+"posted once you log in.</span>"
-#: forum/skins/default/templates/authopenid/signin.html:35
+#: templates/authopenid/signin.html:35
#, python-format
msgid ""
"Your question \n"
" %(title)s %(summary)s will be posted once you log in\n"
" "
msgstr ""
+"<span class=\"strong big\">Your question</span> <i>\"<strong>%(title)s</"
+"strong> %(summary)s...\"</i> <span class=\"strong big\">is saved and will be "
+"posted once you log in.</span>"
-#: forum/skins/default/templates/authopenid/signin.html:42
+#: templates/authopenid/signin.html:42
msgid "Click to sign in through any of these services."
msgstr ""
+"<p><span class=\"big strong\">Please select your favorite login method below."
+"</span></p><p><font color=\"gray\">External login services use <a href="
+"\"http://openid.net\"><b>OpenID</b></a> technology, where your password "
+"always stays confidential between you and your login provider and you don't "
+"have to remember another one. CNPROG option requires your login name and "
+"password entered here.</font></p>"
-#: forum/skins/default/templates/authopenid/signin.html:128
+#: templates/authopenid/signin.html:128
msgid "Enter your <span id=\"enter_your_what\">Provider user name</span>"
msgstr ""
+"<span class=\"big strong\">Enter your </span><span id=\"enter_your_what\" "
+"class='big strong'>Provider user name</span><br/><span class='grey'>(or "
+"select another login method above)</span>"
-#: forum/skins/default/templates/authopenid/signin.html:135
+#: templates/authopenid/signin.html:135
msgid ""
"Enter your <a class=\"openid_logo\" href=\"http://openid.net\">OpenID</a> "
"web address"
msgstr ""
+"<span class=\"big strong\">Enter your <a class=\"openid_logo\" href=\"http://"
+"openid.net\">OpenID</a> web address</span><br/><span class='grey'>(or choose "
+"another login method above)</span>"
-#: forum/skins/default/templates/authopenid/signin.html:137
-#: forum/skins/default/templates/authopenid/signin.html:149
+#: templates/authopenid/signin.html:137 templates/authopenid/signin.html:149
msgid "Login"
msgstr ""
-#: forum/skins/default/templates/authopenid/signin.html:140
+#: templates/authopenid/signin.html:140
msgid "Enter your login name and password"
msgstr ""
+"<span class='big strong'>Enter your CNPROG login and password</span><br/"
+"><span class='grey'>(or select your OpenID provider above)</span>"
-#: forum/skins/default/templates/authopenid/signin.html:144
+#: templates/authopenid/signin.html:144
msgid "Login name"
msgstr ""
-#: forum/skins/default/templates/authopenid/signin.html:146
+#: templates/authopenid/signin.html:146
msgid "Password"
msgstr ""
-#: forum/skins/default/templates/authopenid/signin.html:150
+#: templates/authopenid/signin.html:150
msgid "Create account"
msgstr ""
-#: forum/skins/default/templates/authopenid/signin.html:160
+#: templates/authopenid/signin.html:160
msgid "Why use OpenID?"
msgstr ""
-#: forum/skins/default/templates/authopenid/signin.html:163
+#: templates/authopenid/signin.html:163
msgid "with openid it is easier"
-msgstr ""
+msgstr "With the OpenID you don't need to create new username and password."
-#: forum/skins/default/templates/authopenid/signin.html:166
+#: templates/authopenid/signin.html:166
msgid "reuse openid"
-msgstr ""
+msgstr "You can safely re-use the same login for all OpenID-enabled websites."
-#: forum/skins/default/templates/authopenid/signin.html:169
+#: templates/authopenid/signin.html:169
msgid "openid is widely adopted"
msgstr ""
+"There are > 160,000,000 OpenID account in use. Over 10,000 sites are OpenID-"
+"enabled."
-#: forum/skins/default/templates/authopenid/signin.html:172
+#: templates/authopenid/signin.html:172
msgid "openid is supported open standard"
-msgstr ""
+msgstr "OpenID is based on an open standard, supported by many organizations."
-#: forum/skins/default/templates/authopenid/signin.html:177
+#: templates/authopenid/signin.html:177
msgid "Find out more"
msgstr ""
-#: forum/skins/default/templates/authopenid/signin.html:178
+#: templates/authopenid/signin.html:178
msgid "Get OpenID"
msgstr ""
-#: forum/skins/default/templates/authopenid/signup.html:4
+#: templates/authopenid/signup.html:4
msgid "Signup"
msgstr ""
-#: forum/skins/default/templates/authopenid/signup.html:8
+#: templates/authopenid/signup.html:8
msgid "Create login name and password"
msgstr ""
-#: forum/skins/default/templates/authopenid/signup.html:10
+#: templates/authopenid/signup.html:10
msgid "Traditional signup info"
msgstr ""
+"<span class='strong big'>If you prefer, create your forum login name and "
+"password here. However</span>, please keep in mind that we also support "
+"<strong>OpenID</strong> login method. With <strong>OpenID</strong> you can "
+"simply reuse your external login (e.g. Gmail or AOL) without ever sharing "
+"your login details with anyone and having to remember yet another password."
-#: forum/skins/default/templates/authopenid/signup.html:25
+#: templates/authopenid/signup.html:19
+msgid ""
+"Please select your preferred email update schedule for the following groups "
+"of questions:"
+msgstr ""
+
+#: templates/authopenid/signup.html:23
msgid ""
"Please read and type in the two words below to help us prevent automated "
"account creation."
msgstr ""
-#: forum/skins/default/templates/authopenid/signup.html:27
+#: templates/authopenid/signup.html:25
msgid "Create Account"
msgstr ""
-#: forum/skins/default/templates/authopenid/signup.html:29
+#: templates/authopenid/signup.html:27
msgid "return to OpenID login"
msgstr ""
-#: forum/skins/default/templates/fbconnect/xd_receiver.html:5
+#: templates/fbconnect/xd_receiver.html:5
#, python-format
msgid "Connect to %(APP_SHORT_NAME)s with Facebook!"
msgstr ""
-#: forum/templatetags/extra_tags.py:166 forum/templatetags/extra_tags.py:193
-msgid "reputation points"
-msgstr ""
-
-#: forum/templatetags/extra_tags.py:253
-msgid "2 days ago"
-msgstr ""
-
-#: forum/templatetags/extra_tags.py:255
-msgid "yesterday"
-msgstr ""
-
-#: forum/templatetags/extra_tags.py:257
-#, python-format
-msgid "%(hr)d hour ago"
-msgid_plural "%(hr)d hours ago"
-msgstr[0] ""
-msgstr[1] ""
-
-#: forum/templatetags/extra_tags.py:259
-#, python-format
-msgid "%(min)d min ago"
-msgid_plural "%(min)d mins ago"
-msgstr[0] ""
-msgstr[1] ""
-
-#: forum/views/users.py:876
-msgid "user profile"
-msgstr ""
-
-#: forum/views/users.py:877
-msgid "user profile overview"
-msgstr ""
-
-#: forum/views/users.py:884
-msgid "recent user activity"
-msgstr ""
-
-#: forum/views/users.py:885
-msgid "profile - recent activity"
-msgstr ""
-
-#: forum/views/users.py:894
-msgid "profile - responses"
-msgstr ""
-
-#: forum/views/users.py:902
-msgid "user reputation in the community"
-msgstr ""
-
-#: forum/views/users.py:903
-msgid "profile - user reputation"
-msgstr ""
-
-#: forum/views/users.py:909
-msgid "favorite questions"
-msgstr ""
-
-#: forum/views/users.py:910
-msgid "users favorite questions"
-msgstr ""
-
-#: forum/views/users.py:911
-msgid "profile - favorite questions"
-msgstr ""
-
-#: forum/views/users.py:920
-msgid "profile - votes"
-msgstr ""
-
-#: forum/views/users.py:929
-msgid "profile - email subscriptions"
-msgstr ""
-
-#: middleware/anon_user.py:33
-#, python-format
-msgid "first time greeting with %(url)s"
-msgstr ""
-
-#: templates/unanswered.html:114
-#, python-format
-msgid "have %(num_q)s unanswered questions"
-msgstr ""
-
#: utils/forms.py:27
msgid "this field is required"
msgstr ""
#: utils/forms.py:42
msgid "choose a username"
-msgstr ""
+msgstr "Choose screen name"
#: utils/forms.py:47
msgid "user name is required"
@@ -3423,7 +3441,7 @@ msgstr ""
#: utils/forms.py:100
msgid "your email address"
-msgstr ""
+msgstr "Your email <i>(never shared)</i>"
#: utils/forms.py:101
msgid "email address is required"
@@ -3439,7 +3457,7 @@ msgstr ""
#: utils/forms.py:128
msgid "choose password"
-msgstr ""
+msgstr "Password"
#: utils/forms.py:129
msgid "password is required"
@@ -3447,7 +3465,7 @@ msgstr ""
#: utils/forms.py:132
msgid "retype password"
-msgstr ""
+msgstr "Password <i>(please retype)</i>"
#: utils/forms.py:133
msgid "please, retype your password"
@@ -3456,3 +3474,23 @@ msgstr ""
#: utils/forms.py:134
msgid "sorry, entered passwords did not match, please try again"
msgstr ""
+
+#~ msgid "have %(num_q)s unanswered questions"
+#~ msgstr ""
+#~ "<div class=\"questions-count\">%(num_q)s</div>questions <strong>without "
+#~ "accepted answers</strong>"
+
+#~ msgid ""
+#~ "\n"
+#~ "\t\t\t\thave total %(q_num)s questions\n"
+#~ "\t\t\t\t"
+#~ msgid_plural ""
+#~ "\n"
+#~ "\t\t\t\thave total %(q_num)s questions\n"
+#~ "\t\t\t\t"
+#~ msgstr[0] ""
+#~ "\n"
+#~ "<div class=\"questions-count\">%(q_num)s</div><p>question</p>"
+#~ msgstr[1] ""
+#~ "\n"
+#~ "<div class=\"questions-count\">%(q_num)s</div><p>questions</p>"
diff --git a/osqa.iml b/osqa.iml
index 4e760f0a..f565f75f 100755
--- a/osqa.iml
+++ b/osqa.iml
@@ -1,5 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="django" name="Django">
+ <configuration>
+ <option name="rootFolder" value="$MODULE_DIR$" />
+ <option name="templatesFolder" value="$MODULE_DIR$/templates" />
+ <option name="settingsModule" value="settings.py" />
+ </configuration>
+ </facet>
+ </component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
diff --git a/rmpyc b/rmpyc
new file mode 100755
index 00000000..014575f6
--- /dev/null
+++ b/rmpyc
@@ -0,0 +1 @@
+rm `find . -name '*.pyc'`
diff --git a/session_messages/.svn/all-wcprops b/session_messages/.svn/all-wcprops
deleted file mode 100644
index 2a15b353..00000000
--- a/session_messages/.svn/all-wcprops
+++ /dev/null
@@ -1,23 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 38
-/svn/!svn/ver/5/trunk/session_messages
-END
-__init__.py
-K 25
-svn:wc:ra_dav:version-url
-V 50
-/svn/!svn/ver/5/trunk/session_messages/__init__.py
-END
-models.py
-K 25
-svn:wc:ra_dav:version-url
-V 48
-/svn/!svn/ver/2/trunk/session_messages/models.py
-END
-context_processors.py
-K 25
-svn:wc:ra_dav:version-url
-V 60
-/svn/!svn/ver/2/trunk/session_messages/context_processors.py
-END
diff --git a/session_messages/.svn/dir-prop-base b/session_messages/.svn/dir-prop-base
deleted file mode 100644
index 4cc643b7..00000000
--- a/session_messages/.svn/dir-prop-base
+++ /dev/null
@@ -1,6 +0,0 @@
-K 10
-svn:ignore
-V 6
-*.pyc
-
-END
diff --git a/session_messages/.svn/entries b/session_messages/.svn/entries
deleted file mode 100644
index 67c0db8a..00000000
--- a/session_messages/.svn/entries
+++ /dev/null
@@ -1,64 +0,0 @@
-8
-
-dir
-5
-http://django-session-messages.googlecode.com/svn/trunk/session_messages
-http://django-session-messages.googlecode.com/svn
-
-
-
-2009-03-10T23:30:03.043791Z
-5
-carl.j.meyer
-has-props
-
-svn:special svn:externals svn:needs-lock
-
-
-
-
-
-
-
-
-
-
-
-b8288d2d-7354-0410-af5b-714f73743f4b
-
-__init__.py
-file
-
-
-
-
-2009-10-25T23:36:02.000000Z
-89aa0f71c9973e4889e5fad0b4771a34
-2009-03-10T23:30:03.043791Z
-5
-carl.j.meyer
-
-models.py
-file
-
-
-
-
-2009-10-25T23:36:02.000000Z
-c5b4f274dbb06bc66a14f0c18c9115cd
-2008-08-14T23:13:23.180432Z
-2
-carl.j.meyer
-
-context_processors.py
-file
-
-
-
-
-2009-10-25T23:36:02.000000Z
-24779c7e504d3f7f1918fdf3fe8096bc
-2008-08-14T23:13:23.180432Z
-2
-carl.j.meyer
-
diff --git a/session_messages/.svn/format b/session_messages/.svn/format
deleted file mode 100644
index 45a4fb75..00000000
--- a/session_messages/.svn/format
+++ /dev/null
@@ -1 +0,0 @@
-8
diff --git a/session_messages/.svn/text-base/__init__.py.svn-base b/session_messages/.svn/text-base/__init__.py.svn-base
deleted file mode 100644
index 0136c888..00000000
--- a/session_messages/.svn/text-base/__init__.py.svn-base
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
-Lightweight session-based messaging system.
-
-Time-stamp: <2009-03-10 19:22:29 carljm __init__.py>
-
-"""
-VERSION = (0, 1, 'pre')
-
-def create_message (request, message):
- """
- Create a message in the current session.
-
- """
- assert hasattr(request, 'session'), "django-session-messages requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
-
- try:
- request.session['messages'].append(message)
- except KeyError:
- request.session['messages'] = [message]
-
-def get_and_delete_messages (request, include_auth=False):
- """
- Get and delete all messages for current session.
-
- Optionally also fetches user messages from django.contrib.auth.
-
- """
- assert hasattr(request, 'session'), "django-session-messages requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
-
- messages = request.session.pop('messages', [])
-
- if include_auth and request.user.is_authenticated():
- messages.extend(request.user.get_and_delete_messages())
-
- return messages
-
diff --git a/session_messages/.svn/text-base/context_processors.py.svn-base b/session_messages/.svn/text-base/context_processors.py.svn-base
deleted file mode 100644
index df9840fd..00000000
--- a/session_messages/.svn/text-base/context_processors.py.svn-base
+++ /dev/null
@@ -1,48 +0,0 @@
-"""
-Context processor for lightweight session messages.
-
-Time-stamp: <2008-07-19 23:16:19 carljm context_processors.py>
-
-"""
-from django.utils.encoding import StrAndUnicode
-
-from session_messages import get_and_delete_messages
-
-def session_messages (request):
- """
- Returns session messages for the current session.
-
- """
- return { 'session_messages': LazyMessages(request) }
-
-class LazyMessages (StrAndUnicode):
- """
- Lazy message container, so messages aren't actually retrieved from
- session and deleted until the template asks for them.
-
- """
- def __init__(self, request):
- self.request = request
-
- def __iter__(self):
- return iter(self.messages)
-
- def __len__(self):
- return len(self.messages)
-
- def __nonzero__(self):
- return bool(self.messages)
-
- def __unicode__(self):
- return unicode(self.messages)
-
- def __getitem__(self, *args, **kwargs):
- return self.messages.__getitem__(*args, **kwargs)
-
- def _get_messages(self):
- if hasattr(self, '_messages'):
- return self._messages
- self._messages = get_and_delete_messages(self.request)
- return self._messages
- messages = property(_get_messages)
-
diff --git a/session_messages/.svn/text-base/models.py.svn-base b/session_messages/.svn/text-base/models.py.svn-base
deleted file mode 100644
index b67ead6d..00000000
--- a/session_messages/.svn/text-base/models.py.svn-base
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-blank models.py
-"""
diff --git a/settings.py b/settings.py
index 775bda45..d95118db 100755
--- a/settings.py
+++ b/settings.py
@@ -23,9 +23,9 @@ MIDDLEWARE_CLASSES = [
#'django.middleware.cache.FetchFromCacheMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
#'django.middleware.sqlprint.SqlPrintingMiddleware',
- 'middleware.anon_user.ConnectToSessionMessagesMiddleware',
- 'middleware.pagesize.QuestionsPageSizeMiddleware',
- 'middleware.cancel.CancelActionMiddleware',
+ 'forum.middleware.anon_user.ConnectToSessionMessagesMiddleware',
+ 'forum.middleware.pagesize.QuestionsPageSizeMiddleware',
+ 'forum.middleware.cancel.CancelActionMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'recaptcha_django.middleware.ReCaptchaMiddleware',
'django.middleware.transaction.TransactionMiddleware',
@@ -35,7 +35,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
'context.application_settings',
#'django.core.context_processors.i18n',
- 'user_messages.context_processors.user_messages',#must be before auth
+ 'forum.user_messages.context_processors.user_messages',#must be before auth
'django.core.context_processors.auth', #this is required for admin
)
@@ -69,7 +69,6 @@ INSTALLED_APPS = [
'forum',
'django_authopenid',
'debug_toolbar' ,
- 'user_messages',
]
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend',]
@@ -80,7 +79,7 @@ if USE_SPHINX_SEARCH:
if USE_FB_CONNECT:
INSTALLED_APPS.append('fbconnect')
-if DATABASE_ENGINE in ('postgresql_psycopg2', 'postgresql', ):
+if DATABASE_ENGINE in ('postgresql_psycopg2', 'postgresql', ) and False:
USE_PG_FTS = True
INSTALLED_APPS.append('pgfulltext')
else:
diff --git a/settings_local.py.dist b/settings_local.py.dist
index 2251e58e..52c54bbb 100755
--- a/settings_local.py.dist
+++ b/settings_local.py.dist
@@ -2,20 +2,14 @@
import os.path
from django.utils.translation import ugettext as _
-SITE_SRC_ROOT = os.path.dirname(__file__)
-LOG_FILENAME = 'django.lanai.log'
-
-#for logging
-import logging
-logging.basicConfig(filename=os.path.join(SITE_SRC_ROOT, 'log', LOG_FILENAME), level=logging.DEBUG,)
def check_local_setting(name, value):
local_vars = locals()
- if name in local_vars and local_var[name] == value:
+ if name in local_vars and local_vars[name] == value:
return True
else:
return False
-SITE_SRC_ROOT = os.path.dirname(__file__)
+SITE_SRC_ROOT = os.path.dirname(__file__)
LOG_FILENAME = 'django.osqa.log'
#for logging
@@ -31,17 +25,19 @@ ADMINS = (('Forum Admin', 'forum@example.com'),)
MANAGERS = ADMINS
#DEBUG SETTINGS
-DEBUG = False
+DEBUG = False
TEMPLATE_DEBUG = DEBUG
INTERNAL_IPS = ('127.0.0.1',)
-DATABASE_NAME = 'osqa' # Or path to database file if using sqlite3.
+DATABASE_NAME = '' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
-DATABASE_ENGINE = 'mysql' #mysql, etc
+DATABASE_ENGINE = '' #mysql, etc
DATABASE_HOST = ''
DATABASE_PORT = ''
-
+
+#Moved from settings.py for better organization. (please check it up to clean up settings.py)
+
#email server settings
SERVER_EMAIL = ''
DEFAULT_FROM_EMAIL = ''
@@ -52,14 +48,19 @@ EMAIL_HOST='osqa.net'
EMAIL_PORT='25'
EMAIL_USE_TLS=False
-FORUM_SCRIPT_ALIAS = '' #no leading slash, default = '' empty string
-
#LOCALIZATIONS
-TIME_ZONE = 'Asia/Chongqing Asia/Chungking'
-# LANGUAGE_CODE = 'en-us'
-
+TIME_ZONE = 'America/New_York'
+
+###########################
+#
+# this will allow running your forum with url like http://site.com/forum
+#
+# FORUM_SCRIPT_ALIAS = 'forum/'
+#
+FORUM_SCRIPT_ALIAS = '' #no leading slash, default = '' empty string
+
+
#OTHER SETTINGS
-APP_COPYRIGHT = 'Copyright CNPROG.COM 2009'
APP_TITLE = u'OSQA: Open Source Q&A Forum'
APP_SHORT_NAME = u'OSQA'
APP_KEYWORDS = u'OSQA,CNPROG,forum,community'
@@ -76,8 +77,9 @@ MIN_USERNAME_LENGTH = 1
EMAIL_UNIQUE = False
APP_URL = 'http://osqa.net' #used by email notif system and RSS
GOOGLE_SITEMAP_CODE = ''
-BOOKS_ON = False
-WIKI_ON = True
+GOOGLE_ANALYTICS_KEY = ''
+BOOKS_ON = False
+WIKI_ON = True
USE_EXTERNAL_LEGACY_LOGIN = False
EXTERNAL_LEGACY_LOGIN_HOST = 'login.osqa.net'
EXTERNAL_LEGACY_LOGIN_PORT = 80
@@ -92,7 +94,7 @@ USE_SPHINX_SEARCH = False #if True all SPHINX_* settings are required
#also sphinx search engine and djangosphinxs app must be installed
#sample sphinx configuration file is /sphinx/sphinx.conf
SPHINX_API_VERSION = 0x113 #refer to djangosphinx documentation
-SPHINX_SEARCH_INDICES=('osqa',) #a tuple of index names remember about a comma after the
+SPHINX_SEARCH_INDICES=('osqa',) #a tuple of index names remember about a comma after the
#last item, especially if you have just one :)
SPHINX_SERVER='localhost'
SPHINX_PORT=3312
@@ -100,8 +102,9 @@ SPHINX_PORT=3312
#please get these at recaptcha.net
RECAPTCHA_PRIVATE_KEY='...'
RECAPTCHA_PUBLIC_KEY='...'
+OSQA_DEFAULT_SKIN = 'default'
#Facebook settings
-USE_FB_CONNECT=True
+USE_FB_CONNECT=False
FB_API_KEY='' #your api key from facebook
FB_SECRET='' #your application secret
diff --git a/sql_scripts/update_2010_02_22.sql b/sql_scripts/update_2010_02_22.sql
new file mode 100644
index 00000000..2778885a
--- /dev/null
+++ b/sql_scripts/update_2010_02_22.sql
@@ -0,0 +1 @@
+alter table answer add column deleted_at datetime;
diff --git a/user_messages/models.py b/user_messages/models.py
deleted file mode 100644
index b67ead6d..00000000
--- a/user_messages/models.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-blank models.py
-"""