diff options
-rwxr-xr-x | .idea/compiler.xml | 24 | ||||
-rwxr-xr-x | .idea/copyright/profiles_settings.xml | 5 | ||||
-rwxr-xr-x | .idea/encodings.xml | 5 | ||||
-rwxr-xr-x | .idea/misc.xml | 127 | ||||
-rwxr-xr-x | .idea/modules.xml | 9 | ||||
-rwxr-xr-x | .idea/uiDesigner.xml | 125 | ||||
-rwxr-xr-x | .idea/vcs.xml | 7 | ||||
-rwxr-xr-x | .idea/workspace.xml | 683 | ||||
-rw-r--r-- | forum/admin.py | 18 | ||||
-rw-r--r-- | forum/forms.py | 1 | ||||
-rw-r--r-- | forum/managers.py | 241 | ||||
-rw-r--r-- | forum/models.py | 955 | ||||
-rwxr-xr-x | forum/models/__init__.py | 330 | ||||
-rwxr-xr-x | forum/models/answer.py | 101 | ||||
-rwxr-xr-x | forum/models/base.py | 140 | ||||
-rwxr-xr-x | forum/models/meta.py | 89 | ||||
-rw-r--r-- | forum/models/models.py | 949 | ||||
-rwxr-xr-x | forum/models/q_and_a.py | 97 | ||||
-rwxr-xr-x | forum/models/question.py | 275 | ||||
-rwxr-xr-x | forum/models/repute.py | 107 | ||||
-rwxr-xr-x | forum/models/tag.py | 85 | ||||
-rwxr-xr-x | forum/models/user.py | 67 | ||||
-rw-r--r-- | forum/urls.py | 6 | ||||
-rw-r--r-- | forum/views/__init__.py | 2 | ||||
-rw-r--r-- | forum/views/content.py | 1 | ||||
-rw-r--r-- | forum/views/users.py | 5 | ||||
-rw-r--r-- | pgfulltext/management.py | 2 | ||||
-rwxr-xr-x | settings.py | 2 | ||||
-rwxr-xr-x | tester/__init__.py | 0 | ||||
-rwxr-xr-x | tester/models.py | 8 |
30 files changed, 2304 insertions, 2162 deletions
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..69374f95 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,683 @@ +<?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="378a32c6-5795-4404-9430-96837181eba2" name="Default" comment="">
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/pgfulltext/management.py" afterPath="$PROJECT_DIR$/pgfulltext/management.py" />
+ <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/fb.py" afterPath="$PROJECT_DIR$/fbconnect/fb.py" />
+ <change type="DELETED" beforePath="C:\osqadev\newmaster\osqa\forum\models.py" afterPath="" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/osqa.iml" afterPath="$PROJECT_DIR$/osqa.iml" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/settings.py" afterPath="$PROJECT_DIR$/settings.py" />
+ <change type="DELETED" beforePath="C:\osqadev\newmaster\osqa\forum\models\models.py" afterPath="" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/forms.py" afterPath="$PROJECT_DIR$/forum/forms.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/content.py" afterPath="$PROJECT_DIR$/forum/views/content.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/urls.py" afterPath="$PROJECT_DIR$/fbconnect/urls.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/TODO" afterPath="$PROJECT_DIR$/TODO" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/tests.py" afterPath="$PROJECT_DIR$/fbconnect/tests.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/admin.py" afterPath="$PROJECT_DIR$/forum/admin.py" />
+ <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$/fbconnect/views.py" afterPath="$PROJECT_DIR$/fbconnect/views.py" />
+ <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/skins/default/templates/fbconnect/xd_receiver.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/fbconnect/xd_receiver.html" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/users.py" afterPath="$PROJECT_DIR$/forum/views/users.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/__init__.py" afterPath="$PROJECT_DIR$/forum/views/__init__.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/models.py" afterPath="$PROJECT_DIR$/fbconnect/models.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/pjson.py" afterPath="$PROJECT_DIR$/fbconnect/pjson.py" />
+ <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$/rmpyc" afterPath="$PROJECT_DIR$/rmpyc" />
+ <change type="DELETED" beforePath="C:\osqadev\newmaster\osqa\forum\managers.py" afterPath="" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/django_authopenid/urls.py" afterPath="$PROJECT_DIR$/django_authopenid/urls.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/forms.py" afterPath="$PROJECT_DIR$/fbconnect/forms.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/django_authopenid/views.py" afterPath="$PROJECT_DIR$/django_authopenid/views.py" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/fbconnect/__init__.py" afterPath="$PROJECT_DIR$/fbconnect/__init__.py" />
+ <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$/forum/urls.py" afterPath="$PROJECT_DIR$/forum/urls.py" />
+ </list>
+ <ignored path=".idea/workspace.xml" />
+ <ignored path="osqa.iws" />
+ <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="question.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/models/question.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="18" column="68" selection-start="710" selection-end="710" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="answer.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/models/answer.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="24" column="42" selection-start="746" selection-end="746" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="meta.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/models/meta.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="17" column="12" selection-start="496" selection-end="525" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="q_and_a.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/models/q_and_a.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="58" column="23" selection-start="2054" selection-end="2054" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="repute.py" pinned="false" current="true" current-in-tab="true">
+ <entry file="file://$PROJECT_DIR$/forum/models/repute.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="83" column="87" selection-start="3337" selection-end="3337" vertical-scroll-proportion="0.69195753">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="tag.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/models/tag.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="64" column="74" selection-start="2412" selection-end="2412" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="base.py" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/forum/models/base.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="111" column="59" selection-start="4050" selection-end="4050" 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.py" />
+ <option value="$PROJECT_DIR$/settings_local.py" />
+ <option value="$PROJECT_DIR$/forum/models/tag.py" />
+ <option value="$PROJECT_DIR$/forum/models/user.py" />
+ <option value="$PROJECT_DIR$/forum/models/__init__.py" />
+ <option value="$PROJECT_DIR$/forum/admin.py" />
+ <option value="$PROJECT_DIR$/forum/views/__init__.py" />
+ <option value="$PROJECT_DIR$/forum/urls.py" />
+ <option value="$PROJECT_DIR$/forum/views/users.py" />
+ <option value="$PROJECT_DIR$/forum/views/content.py" />
+ <option value="$PROJECT_DIR$/forum/models/base.py" />
+ <option value="$PROJECT_DIR$/forum/models/meta.py" />
+ <option value="$PROJECT_DIR$/forum/models/answer.py" />
+ <option value="$PROJECT_DIR$/forum/models/question.py" />
+ <option value="$PROJECT_DIR$/forum/models/q_and_a.py" />
+ <option value="$PROJECT_DIR$/forum/models/repute.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="Scope" />
+ <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>
+ <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_ELEMENT>
+ <option name="myItemId" value="tester" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+ </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_ELEMENT>
+ <option name="myItemId" value="log" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+ </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_ELEMENT>
+ <option name="myItemId" value="forum" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+ </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_ELEMENT>
+ <option name="myItemId" value="forum" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="models" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ </subPane>
+ </pane>
+ <pane id="Favorites" />
+ <pane id="PackagesPane" />
+ </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="StructureViewFactory">
+ <option name="ACTIVE_ACTIONS" value="ALPHA_COMPARATOR" />
+ </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">
+ <changelist id="378a32c6-5795-4404-9430-96837181eba2" name="Default" comment="" />
+ <created>1266231540266</created>
+ <updated>1266231540266</updated>
+ </task>
+ <servers />
+ </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="2" 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="7" 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="3" 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="11" 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="5" 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="8" 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.33" 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.24959481" sideWeight="0.7080182" 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="4" 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="9" 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.24939074" 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="10" 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="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" />
+ <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" />
+ </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/admin.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="73" column="1" selection-start="1937" selection-end="1937" vertical-scroll-proportion="0.0" />
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/views/__init__.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="3" column="1" selection-start="41" selection-end="41" vertical-scroll-proportion="0.0" />
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/urls.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="86" column="5" selection-start="5472" selection-end="5472" vertical-scroll-proportion="0.0" />
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/models/__init__.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="17" column="11" selection-start="491" selection-end="491" vertical-scroll-proportion="0.44875777" />
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/log/django.osqa.log">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="196" column="47" selection-start="27912" selection-end="27913" vertical-scroll-proportion="0.42326334" />
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/views/users.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="278" column="28" selection-start="11173" selection-end="11173" vertical-scroll-proportion="0.0" />
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/skins/default/templates/index.html">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="156" column="15" selection-start="7611" selection-end="7611" vertical-scroll-proportion="-10.755102">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/models/user.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="2" column="0" selection-start="20" selection-end="70" vertical-scroll-proportion="0.0" />
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/views/content.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="1042" column="23" selection-start="43546" selection-end="43546" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/models/tag.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="64" column="74" selection-start="2412" selection-end="2412" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/models/base.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="111" column="59" selection-start="4050" selection-end="4050" vertical-scroll-proportion="0.0" />
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/models/question.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="18" column="68" selection-start="710" selection-end="710" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/models/meta.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="17" column="12" selection-start="496" selection-end="525" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/models/answer.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="24" column="42" selection-start="746" selection-end="746" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/models/q_and_a.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="58" column="23" selection-start="2054" selection-end="2054" vertical-scroll-proportion="0.0">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/forum/models/repute.py">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="83" column="87" selection-start="3337" selection-end="3337" vertical-scroll-proportion="0.69195753">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </component>
+</project>
+
diff --git a/forum/admin.py b/forum/admin.py index 482da048..88643b92 100644 --- a/forum/admin.py +++ b/forum/admin.py @@ -46,14 +46,14 @@ class ReputeAdmin(admin.ModelAdmin): class ActivityAdmin(admin.ModelAdmin): """ admin class""" -class BookAdmin(admin.ModelAdmin): - """ admin class""" +#class BookAdmin(admin.ModelAdmin): +# """ admin class""" -class BookAuthorInfoAdmin(admin.ModelAdmin): - """ admin class""" +#class BookAuthorInfoAdmin(admin.ModelAdmin): +# """ admin class""" -class BookAuthorRssAdmin(admin.ModelAdmin): - """ admin class""" +#class BookAuthorRssAdmin(admin.ModelAdmin): +# """ admin class""" admin.site.register(Question, QuestionAdmin) @@ -69,6 +69,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) +#admin.site.register(Book, BookAdmin) +#admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin) +#admin.site.register(BookAuthorRss, BookAuthorRssAdmin) diff --git a/forum/forms.py b/forum/forms.py index 308f853b..5796e2c1 100644 --- a/forum/forms.py +++ b/forum/forms.py @@ -4,6 +4,7 @@ from django import forms from models import * from const import * from django.utils.translation import ugettext as _ +from django.contrib.auth.models import User from utils.forms import NextUrlField, UserNameField from recaptcha_django import ReCaptchaField from django.conf import settings diff --git a/forum/managers.py b/forum/managers.py deleted file mode 100644 index 3f580bd3..00000000 --- a/forum/managers.py +++ /dev/null @@ -1,241 +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() - 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): - """ - 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(Q(question=question), - Q(deleted=False) | Q(deleted_by=user)) - - 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/forum/models.py b/forum/models.py deleted file mode 100644 index 2da8ef7b..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..71c2e902 --- /dev/null +++ b/forum/models/__init__.py @@ -0,0 +1,330 @@ +from question import Question
+from answer import Answer
+from q_and_a import QuestionRevision, QuestionView, AnonymousAnswer, AnonymousQuestion, AnswerRevision, FavoriteQuestion
+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',
+ ]
diff --git a/forum/models/answer.py b/forum/models/answer.py new file mode 100755 index 00000000..92a86085 --- /dev/null +++ b/forum/models/answer.py @@ -0,0 +1,101 @@ +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)
+
+ #update revision
+ from models import AnswerRevision
+
+ 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(Q(question=question),
+ Q(deleted=False) | Q(deleted_by=user))
+
+ 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
\ No newline at end of file diff --git a/forum/models/base.py b/forum/models/base.py new file mode 100755 index 00000000..24fe2b0d --- /dev/null +++ b/forum/models/base.py @@ -0,0 +1,140 @@ +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 *
+
+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..3dfd3e86 --- /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/models.py b/forum/models/models.py deleted file mode 100644 index acde1033..00000000 --- a/forum/models/models.py +++ /dev/null @@ -1,949 +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 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 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' - -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) diff --git a/forum/models/q_and_a.py b/forum/models/q_and_a.py new file mode 100755 index 00000000..76266e80 --- /dev/null +++ b/forum/models/q_and_a.py @@ -0,0 +1,97 @@ +from base import *
+
+from question import Question, QuestionManager
+
+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
+
+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()
\ No newline at end of file diff --git a/forum/models/question.py b/forum/models/question.py new file mode 100755 index 00000000..6db2bdc0 --- /dev/null +++ b/forum/models/question.py @@ -0,0 +1,275 @@ +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()
+
+ from models import QuestionRevision
+
+ # 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 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 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()
+ 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)
+
+ 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
+
+ from models import FavoriteQuestion
+ return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0
+
+ def get_answer_count_by_user(self, user_id):
+ from models 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
diff --git a/forum/models/repute.py b/forum/models/repute.py new file mode 100755 index 00000000..768dbee6 --- /dev/null +++ b/forum/models/repute.py @@ -0,0 +1,107 @@ +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)
+
+ 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..28b9e572 --- /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..4e1a376d --- /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/urls.py b/forum/urls.py index fe335538..7fa03706 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -82,9 +82,9 @@ urlpatterns = patterns('', (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$' % _('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$' % _('feedback/'), app.meta.feedback, name='feedback'), (r'^%sfb/' % _('account/'), include('fbconnect.urls')), diff --git a/forum/views/__init__.py b/forum/views/__init__.py index 7fdb6f61..9c112c9b 100644 --- a/forum/views/__init__.py +++ b/forum/views/__init__.py @@ -1,4 +1,4 @@ import content import users import meta -import books +#import books diff --git a/forum/views/content.py b/forum/views/content.py index 2833edfc..9506fe3a 100644 --- a/forum/views/content.py +++ b/forum/views/content.py @@ -20,6 +20,7 @@ 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 django.core.urlresolvers import reverse from utils.html import sanitize_html from utils.decorators import ajax_method, ajax_login_required diff --git a/forum/views/users.py b/forum/views/users.py index 03d38834..965d3a68 100644 --- a/forum/views/users.py +++ b/forum/views/users.py @@ -8,6 +8,7 @@ from django.http import HttpResponse, HttpResponseForbidden from django.utils.translation import ugettext as _ from forum.forms import *#incomplete list is EditUserForm, ModerateUserForm, TagFilterSelectionForm, from forum import auth +from django.contrib.contenttypes.models import ContentType question_type = ContentType.objects.get_for_model(Question) answer_type = ContentType.objects.get_for_model(Answer) @@ -99,8 +100,8 @@ USER_TEMPLATE_VIEWS = ( 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) + from forum.views import users + func = getattr(users, user_view.view_name) return func(request, id, user_view) diff --git a/pgfulltext/management.py b/pgfulltext/management.py index 04303092..46f51edc 100644 --- a/pgfulltext/management.py +++ b/pgfulltext/management.py @@ -9,7 +9,7 @@ if settings.USE_PG_FTS: from django.db.models.signals import post_syncdb def setup_pgfulltext(sender, **kwargs): - if sender == forum.models: + if sender == forum.models_: install_pg_fts() post_syncdb.connect(setup_pgfulltext) diff --git a/settings.py b/settings.py index 3ffc6d4f..b384e3fa 100755 --- a/settings.py +++ b/settings.py @@ -81,7 +81,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/tester/__init__.py b/tester/__init__.py new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/tester/__init__.py diff --git a/tester/models.py b/tester/models.py new file mode 100755 index 00000000..2029cc56 --- /dev/null +++ b/tester/models.py @@ -0,0 +1,8 @@ +from django.db import models
+
+class Tag(models.Model):
+ name = models.CharField(max_length=255, unique=True)
+
+class Question(models.Model):
+ title = models.CharField(max_length=300)
+ tags = models.ManyToManyField(Tag, related_name='questions')
\ No newline at end of file |