diff options
107 files changed, 7391 insertions, 8456 deletions
@@ -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="<template>" 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>
+
@@ -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 }}">●</span> {{ 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 }}">●</span> {{ 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 }}">●</span> {{ 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">×{{ 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">× {{ 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">× {{ 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 Binary files differindex b87457df..38120e49 100644 --- a/locale/en/LC_MESSAGES/django.mo +++ b/locale/en/LC_MESSAGES/django.mo 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&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&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&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&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&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&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&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/>• submit your " +"user name below and check your email<br/>• <strong>follow the " +"activation link</strong> for the new password - sent to you by email and " +"login with the suggested password<br/>• 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>" @@ -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$" />
@@ -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 -""" |