diff options
319 files changed, 9174 insertions, 4111 deletions
@@ -2,7 +2,7 @@ *.swp *.log cache/?? -osqa.wsgi +*.wsgi nbproject settings_local.py .idea diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100755 index b9a1798a..00000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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 deleted file mode 100755 index b385f01f..00000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<component name="CopyrightManager">
- <settings default="">
- <module2copyright />
- </settings>
-</component>
\ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100755 index 7c62b52a..00000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?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 deleted file mode 100755 index 26b17735..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,315 +0,0 @@ -<?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="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="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="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="ModuleEditorState">
- <option name="LAST_EDITED_MODULE_NAME" />
- <option name="LAST_EDITED_TAB_NAME" />
- </component>
- <component name="ProjectDetails">
- <option name="projectName" value="osqa" />
- </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" />
- <ConfirmationsSetting value="0" id="Add" />
- <ConfirmationsSetting value="0" id="Remove" />
- </component>
- <component name="ProjectReloadState">
- <option name="STATE" value="0" />
- </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="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="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="Applet" factoryName="Applet">
- <module name="" />
- <option name="MAIN_CLASS_NAME" />
- <option name="HTML_FILE_NAME" />
- <option name="HTML_USED" value="false" />
- <option name="WIDTH" value="400" />
- <option name="HEIGHT" value="300" />
- <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" />
- <option name="VM_PARAMETERS" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
- <option name="ALTERNATIVE_JRE_PATH" />
- <method>
- <option name="AntTarget" enabled="false" />
- <option name="BuildArtifacts" enabled="false" />
- <option name="Make" enabled="true" />
- <option name="Maven.BeforeRunTask" enabled="false" />
- </method>
- </configuration>
- <configuration default="true" type="Application" factoryName="Application">
- <extension name="coverage" enabled="false" merge="false" />
- <extension name="snapshooter" />
- <option name="MAIN_CLASS_NAME" />
- <option name="VM_PARAMETERS" />
- <option name="PROGRAM_PARAMETERS" />
- <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
- <option name="ALTERNATIVE_JRE_PATH" />
- <option name="ENABLE_SWING_INSPECTOR" value="false" />
- <option name="ENV_VARIABLES" />
- <option name="PASS_PARENT_ENVS" value="true" />
- <module name="" />
- <envs />
- <method>
- <option name="AntTarget" enabled="false" />
- <option name="BuildArtifacts" enabled="false" />
- <option name="Make" enabled="true" />
- <option name="Maven.BeforeRunTask" enabled="false" />
- </method>
- </configuration>
- <configuration default="true" type="JUnit" factoryName="JUnit">
- <extension name="coverage" enabled="false" merge="false" />
- <extension name="snapshooter" />
- <module name="" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
- <option name="ALTERNATIVE_JRE_PATH" />
- <option name="PACKAGE_NAME" />
- <option name="MAIN_CLASS_NAME" />
- <option name="METHOD_NAME" />
- <option name="TEST_OBJECT" value="class" />
- <option name="VM_PARAMETERS" />
- <option name="PARAMETERS" />
- <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
- <option name="ENV_VARIABLES" />
- <option name="PASS_PARENT_ENVS" value="true" />
- <option name="TEST_SEARCH_SCOPE">
- <value defaultName="moduleWithDependencies" />
- </option>
- <envs />
- <method>
- <option name="AntTarget" enabled="false" />
- <option name="BuildArtifacts" enabled="false" />
- <option name="Make" enabled="true" />
- <option name="Maven.BeforeRunTask" enabled="false" />
- </method>
- </configuration>
- <list size="0" />
- <configuration name="<template>" type="WebApp" default="true" selected="false">
- <Host>localhost</Host>
- <Port>5050</Port>
- </configuration>
- </component>
- <component name="ShelveChangesManager" show_recycled="false" />
- <component name="StarteamConfiguration">
- <option name="SERVER" value="" />
- <option name="PORT" value="49201" />
- <option name="USER" value="" />
- <option name="PASSWORD" value="" />
- <option name="PROJECT" value="" />
- <option name="VIEW" value="" />
- <option name="ALTERNATIVE_WORKING_PATH" value="" />
- <option name="LOCK_ON_CHECKOUT" value="false" />
- <option name="UNLOCK_ON_CHECKIN" value="false" />
- </component>
- <component name="SvnConfiguration">
- <option name="USER" value="" />
- <option name="PASSWORD" value="" />
- <option name="LAST_MERGED_REVISION" />
- <option name="UPDATE_RUN_STATUS" value="false" />
- <option name="MERGE_DRY_RUN" value="false" />
- <option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
- <option name="UPDATE_LOCK_ON_DEMAND" value="false" />
- <option name="IGNORE_SPACES_IN_MERGE" value="false" />
- <option name="DETECT_NESTED_COPIES" value="false" />
- <option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
- <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
- <myIsUseDefaultProxy>false</myIsUseDefaultProxy>
- </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 deleted file mode 100755 index 9a64d92a..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?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 deleted file mode 100755 index 1e7cce4b..00000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,125 +0,0 @@ -<?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 deleted file mode 100755 index 7e76f0fd..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?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 deleted file mode 100755 index dde6175a..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,1090 +0,0 @@ -<?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">
- <list default="true" readonly="true" id="df1b69cf-9c35-4a78-9890-c59b542399e8" name="Default" comment="">
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/book.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/book.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/urls.py" afterPath="$PROJECT_DIR$/urls.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/auth.py" afterPath="$PROJECT_DIR$/forum/auth.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/pagesize.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/pagesize.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/close.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/close.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/facebook.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/facebook.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/openid-inputicon.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/openid-inputicon.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/facebookauth/templates/xd_receiver.html" afterPath="$PROJECT_DIR$/forum_modules/facebookauth/templates/xd_receiver.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/wmd.css" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/wmd.css" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/auth.py" afterPath="$PROJECT_DIR$/forum/views/auth.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/vote-favorite-on.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/vote-favorite-on.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/technorati.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/technorati.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.flot.pack.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.flot.pack.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/email_base.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/email_base.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/feedback_email.txt" afterPath="$PROJECT_DIR$/forum/skins/default/templates/feedback_email.txt" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/books/views.py" afterPath="$PROJECT_DIR$/forum_modules/books/views.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/osqa.iml" afterPath="$PROJECT_DIR$/osqa.iml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/tags.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/tags.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/tag.py" afterPath="$PROJECT_DIR$/forum/models/tag.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/pgfulltext/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/pgfulltext/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/sphinxfulltext/dependencies.py" afterPath="$PROJECT_DIR$/forum_modules/sphinxfulltext/dependencies.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/compress.bat" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/compress.bat" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/email.py" afterPath="$PROJECT_DIR$/forum/utils/email.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/ask.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/ask.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/users_questions.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/users_questions.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/vote-favorite-off.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/vote-favorite-off.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/cron/multi_award_badges_virtualenv" afterPath="$PROJECT_DIR$/cron/multi_award_badges_virtualenv" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2010_02_22.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2010_02_22.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/management/commands/subscribe_everyone.py" afterPath="$PROJECT_DIR$/forum/management/commands/subscribe_everyone.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/medala.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/medala.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/question_edit.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/question_edit.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.tag_selector.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.tag_selector.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/locale/es/LC_MESSAGES/django.po" afterPath="$PROJECT_DIR$/locale/es/LC_MESSAGES/django.po" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/claimid.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/claimid.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.i18n.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.i18n.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/osqaadmin/index.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/osqaadmin/index.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/decorators.py" afterPath="$PROJECT_DIR$/forum/utils/decorators.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/pgfulltext/handlers.py" afterPath="$PROJECT_DIR$/forum_modules/pgfulltext/handlers.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/cc-88x31.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/cc-88x31.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/logo.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/logo.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/openidauth/consumer.py" afterPath="$PROJECT_DIR$/forum_modules/openidauth/consumer.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$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/footer.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/footer.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/revisions_answer.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/revisions_answer.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_12_27_001.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_12_27_001.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/vote-arrow-up-on.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/vote-arrow-up-on.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/INSTALL" afterPath="$PROJECT_DIR$/INSTALL" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_01_13_002.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_01_13_002.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/openid.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/openid.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_01_13_001.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_01_13_001.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/localauth/authentication.py" afterPath="$PROJECT_DIR$/forum_modules/localauth/authentication.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/sphinxfulltext/README.txt" afterPath="$PROJECT_DIR$/forum_modules/sphinxfulltext/README.txt" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/settings/__init__.py" afterPath="$PROJECT_DIR$/forum/settings/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/__init__.py" afterPath="$PROJECT_DIR$/forum/skins/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/vote-arrow-up.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/vote-arrow-up.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/aol.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/aol.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/account_settings.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/account_settings.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/pgfulltext/pg_fts_install.sql" afterPath="$PROJECT_DIR$/forum_modules/pgfulltext/pg_fts_install.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/compiler.xml" afterPath="$PROJECT_DIR$/.idea/compiler.xml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/500.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/500.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/facebookauth/settings.py" afterPath="$PROJECT_DIR$/forum_modules/facebookauth/settings.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/flickr.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/flickr.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/uiDesigner.xml" afterPath="$PROJECT_DIR$/.idea/uiDesigner.xml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_07_05_EF.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_07_05_EF.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/default_badges/settings.py" afterPath="$PROJECT_DIR$/forum_modules/default_badges/settings.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/livejournal.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/livejournal.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/commands.py" afterPath="$PROJECT_DIR$/forum/views/commands.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/HOW_TO_DEBUG" afterPath="$PROJECT_DIR$/HOW_TO_DEBUG" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.utils.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.utils.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/sitemap.py" afterPath="$PROJECT_DIR$/forum/sitemap.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/wordpress.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/wordpress.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/wmd-min.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/wmd-min.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/question_summary_list_roll.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/question_summary_list_roll.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/project_badges/settings.py" afterPath="$PROJECT_DIR$/forum_modules/project_badges/settings.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/sphinxfulltext/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/sphinxfulltext/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/blogger-1.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/blogger-1.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/privacy.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/privacy.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/badges/__init__.py" afterPath="$PROJECT_DIR$/forum/badges/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/INSTALL.pip" afterPath="$PROJECT_DIR$/INSTALL.pip" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/reopen.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/reopen.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/recaptcha/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/recaptcha/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/management/commands/send_email_alerts.py" afterPath="$PROJECT_DIR$/forum/management/commands/send_email_alerts.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/livejournal.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/livejournal.ico" />
- <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/readers.py" afterPath="$PROJECT_DIR$/forum/views/readers.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/edit.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/edit.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/blue-up-arrow-h18px.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/blue-up-arrow-h18px.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/user_messages/context_processors.py" afterPath="$PROJECT_DIR$/forum/user_messages/context_processors.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/wmd.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/wmd.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/style/auth.css" afterPath="$PROJECT_DIR$/forum/skins/default/media/style/auth.css" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/time.py" afterPath="$PROJECT_DIR$/forum/utils/time.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/recaptcha/formfield.py" afterPath="$PROJECT_DIR$/forum_modules/recaptcha/formfield.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/management/__init__.py" afterPath="$PROJECT_DIR$/forum/management/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/utils.py" afterPath="$PROJECT_DIR$/forum/models/utils.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/user_messages/__init__.py" afterPath="$PROJECT_DIR$/forum/user_messages/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/__init__.py" afterPath="$PROJECT_DIR$/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/se_hilite.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/se_hilite.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/bullet_green.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/bullet_green.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/PENDING" afterPath="$PROJECT_DIR$/PENDING" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/recaptcha/handlers.py" afterPath="$PROJECT_DIR$/forum_modules/recaptcha/handlers.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/middleware/__init__.py" afterPath="$PROJECT_DIR$/forum/middleware/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/yahoo.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/yahoo.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/osqa-requirements.txt" afterPath="$PROJECT_DIR$/osqa-requirements.txt" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/forms.py" afterPath="$PROJECT_DIR$/forum/utils/forms.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/management/commands/sample_command.py" afterPath="$PROJECT_DIR$/forum/management/commands/sample_command.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/answer.py" afterPath="$PROJECT_DIR$/forum/models/answer.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/locale/en/LC_MESSAGES/django.po" afterPath="$PROJECT_DIR$/locale/en/LC_MESSAGES/django.po" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/middleware/anon_user.py" afterPath="$PROJECT_DIR$/forum/middleware/anon_user.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/logo.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/logo.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/style/openid.css" afterPath="$PROJECT_DIR$/forum/skins/default/media/style/openid.css" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/showdown.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/showdown.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/localauth/views.py" afterPath="$PROJECT_DIR$/forum_modules/localauth/views.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/locale/es/LC_MESSAGES/django.mo" afterPath="$PROJECT_DIR$/locale/es/LC_MESSAGES/django.mo" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/auth/signup.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/auth/signup.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.validate.pack.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.validate.pack.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/localauth/urls.py" afterPath="$PROJECT_DIR$/forum_modules/localauth/urls.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/users.py" afterPath="$PROJECT_DIR$/forum/views/users.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/grippie.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/grippie.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/style/prettify.css" afterPath="$PROJECT_DIR$/forum/skins/default/media/style/prettify.css" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/faq.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/faq.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/tag_selector.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/tag_selector.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/aol.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/aol.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/flickr.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/flickr.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/openid.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/openid.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/images/wmd-buttons.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/images/wmd-buttons.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_footer.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_footer.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/vote-accepted-on.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/vote-accepted-on.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/auth/complete.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/auth/complete.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/encodings.xml" afterPath="$PROJECT_DIR$/.idea/encodings.xml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/flot-build.bat" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/flot-build.bat" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/user.py" afterPath="$PROJECT_DIR$/forum/models/user.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/vote-arrow-down-on.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/vote-arrow-down-on.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/new.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/new.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/recaptcha/lib/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/recaptcha/lib/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sphinx/sphinx.conf" afterPath="$PROJECT_DIR$/sphinx/sphinx.conf" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/claimid-0.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/claimid-0.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/sphinxfulltext/models.py" afterPath="$PROJECT_DIR$/forum_modules/sphinxfulltext/models.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/yahoo.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/yahoo.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/dot-g.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/dot-g.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/settings/forms.py" afterPath="$PROJECT_DIR$/forum/settings/forms.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/settings/pages.py" afterPath="$PROJECT_DIR$/forum/settings/pages.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/vote-arrow-down.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/vote-arrow-down.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/users.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/users.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/openidauth/templates/openidurl.html" afterPath="$PROJECT_DIR$/forum_modules/openidauth/templates/openidurl.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/localauth/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/localauth/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/lists.py" afterPath="$PROJECT_DIR$/forum/utils/lists.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/vidoop.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/vidoop.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/locale/zh_CN/LC_MESSAGES/django.po" afterPath="$PROJECT_DIR$/locale/zh_CN/LC_MESSAGES/django.po" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/startup.py" afterPath="$PROJECT_DIR$/forum/startup.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/openid-inputicon.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/openid-inputicon.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/facebookauth/views.py" afterPath="$PROJECT_DIR$/forum_modules/facebookauth/views.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/vidoop.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/vidoop.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/pg_fts_install.sql" afterPath="$PROJECT_DIR$/sql_scripts/pg_fts_install.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/djangomade124x25_grey.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/djangomade124x25_grey.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/twitter.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/twitter.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/misc.xml" afterPath="$PROJECT_DIR$/.idea/misc.xml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/google.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/google.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/authentication/__init__.py" afterPath="$PROJECT_DIR$/forum/authentication/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/oauthauth/lib/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/oauthauth/lib/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/base.py" afterPath="$PROJECT_DIR$/forum/models/base.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/templatetags/extra_tags.py" afterPath="$PROJECT_DIR$/forum/templatetags/extra_tags.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/openidauth/models.py" afterPath="$PROJECT_DIR$/forum_modules/openidauth/models.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/feed-icon-small.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/feed-icon-small.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/style/style.css" afterPath="$PROJECT_DIR$/forum/skins/default/media/style/style.css" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/close-small.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/close-small.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/osqa.wsgi.dist" afterPath="$PROJECT_DIR$/osqa.wsgi.dist" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/dos2unix.sh" afterPath="$PROJECT_DIR$/dos2unix.sh" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/modules.xml" afterPath="$PROJECT_DIR$/.idea/modules.xml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/authentication/base.py" afterPath="$PROJECT_DIR$/forum/authentication/base.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/index.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/index.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/facebookauth/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/facebookauth/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/quest-bg.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/quest-bg.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/locale/zh_CN/LC_MESSAGES/django.mo" afterPath="$PROJECT_DIR$/locale/zh_CN/LC_MESSAGES/django.mo" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/091208_upgrade_evgeny.sql" afterPath="$PROJECT_DIR$/sql_scripts/091208_upgrade_evgeny.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/management/commands/message_to_everyone.py" afterPath="$PROJECT_DIR$/forum/management/commands/message_to_everyone.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/base.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/base.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/question_edit_tips.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/question_edit_tips.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/const.py" afterPath="$PROJECT_DIR$/forum/const.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/cron/once_award_badges_virtualenv" afterPath="$PROJECT_DIR$/cron/once_award_badges_virtualenv" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/question.py" afterPath="$PROJECT_DIR$/forum/models/question.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/logo1.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/logo1.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/livejournal-1.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/livejournal-1.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_01_18_001.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_01_18_001.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/books/urls.py" afterPath="$PROJECT_DIR$/forum_modules/books/urls.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/jquery-1.2.6.min.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/jquery-1.2.6.min.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/auth/email_validation.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/auth/email_validation.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_favorites.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_favorites.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/__init__.py" afterPath="$PROJECT_DIR$/forum/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/repute.py" afterPath="$PROJECT_DIR$/forum/models/repute.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/style/default.css" afterPath="$PROJECT_DIR$/forum/skins/default/media/style/default.css" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/technorati.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/technorati.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/forms.py" afterPath="$PROJECT_DIR$/forum/forms.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/style/admin.css" afterPath="$PROJECT_DIR$/forum/skins/default/media/style/admin.css" />
- <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/skins/default/media/js/wmd/showdown-min.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/showdown-min.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/flickr.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/flickr.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/cron/send_email_alerts_virtualenv" afterPath="$PROJECT_DIR$/cron/send_email_alerts_virtualenv" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/log/README.TXT" afterPath="$PROJECT_DIR$/log/README.TXT" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/technorati-1.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/technorati-1.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/auth/temp_login_request.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/auth/temp_login_request.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/cnprog_new_install.sql" afterPath="$PROJECT_DIR$/sql_scripts/cnprog_new_install.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/robotstxt/settings.py" afterPath="$PROJECT_DIR$/forum_modules/robotstxt/settings.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/modules.py" afterPath="$PROJECT_DIR$/forum/modules.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/common/media/README" afterPath="$PROJECT_DIR$/forum/skins/common/media/README" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/openidauth/authentication.py" afterPath="$PROJECT_DIR$/forum_modules/openidauth/authentication.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/dash.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/dash.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/robotstxt/templates/robots.txt" afterPath="$PROJECT_DIR$/forum_modules/robotstxt/templates/robots.txt" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/authentication/forms.py" afterPath="$PROJECT_DIR$/forum/authentication/forms.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/close-small-hover.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/close-small-hover.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/writers.py" afterPath="$PROJECT_DIR$/forum/views/writers.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/books/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/books/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/verisign.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/verisign.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/auth/temp_login_email.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/auth/temp_login_email.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/myopenid.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/myopenid.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/recaptcha/lib/captcha.py" afterPath="$PROJECT_DIR$/forum_modules/recaptcha/lib/captcha.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/openidico16.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/openidico16.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/badges.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/badges.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/default_badges/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/default_badges/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/verisign.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/verisign.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/__init__.py" afterPath="$PROJECT_DIR$/forum/views/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/se_hilite_src.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/se_hilite_src.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/expander-arrow-show.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/expander-arrow-show.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/style/jquery.autocomplete.css" afterPath="$PROJECT_DIR$/forum/skins/default/media/style/jquery.autocomplete.css" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/answer_edit_tips.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/answer_edit_tips.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/verisign-2.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/verisign-2.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/odict.py" afterPath="$PROJECT_DIR$/forum/utils/odict.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/cron/send_email_alerts" afterPath="$PROJECT_DIR$/cron/send_email_alerts" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/default_badges/badges.py" afterPath="$PROJECT_DIR$/forum_modules/default_badges/badges.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/logo2.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/logo2.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_01_24.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_01_24.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/claimid.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/claimid.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/osqaadmin/base.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/osqaadmin/base.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/management/commands/base_command.py" afterPath="$PROJECT_DIR$/forum/management/commands/base_command.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/upfiles/README" afterPath="$PROJECT_DIR$/forum/upfiles/README" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/openidauth/store.py" afterPath="$PROJECT_DIR$/forum_modules/openidauth/store.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/favicon.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/favicon.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/pgfulltext/management.py" afterPath="$PROJECT_DIR$/forum_modules/pgfulltext/management.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/nophoto.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/nophoto.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/project_badges/badges.py" afterPath="$PROJECT_DIR$/forum_modules/project_badges/badges.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/README" afterPath="$PROJECT_DIR$/forum/views/README" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/oauthauth/authentication.py" afterPath="$PROJECT_DIR$/forum_modules/oauthauth/authentication.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$/forum/skins/default/media/images/indicator.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/indicator.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/facebookauth/authentication.py" afterPath="$PROJECT_DIR$/forum_modules/facebookauth/authentication.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/settings_local.py.dist" afterPath="$PROJECT_DIR$/settings_local.py.dist" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/myopenid-2.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/myopenid-2.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/drop-auth.sql" afterPath="$PROJECT_DIR$/sql_scripts/drop-auth.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_stats.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_stats.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/medala_on.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/medala_on.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/cnprog_new_install_2009_03_31.sql" afterPath="$PROJECT_DIR$/sql_scripts/cnprog_new_install_2009_03_31.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/question_retag.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/question_retag.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_votes.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_votes.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/yuicompressor-2.4.2.jar" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/yuicompressor-2.4.2.jar" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/blogger.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/blogger.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.i18n.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.i18n.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/sphinxfulltext/handlers.py" afterPath="$PROJECT_DIR$/forum_modules/sphinxfulltext/handlers.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/models/meta.py" afterPath="$PROJECT_DIR$/forum/models/meta.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/google.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/google.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/diff.py" afterPath="$PROJECT_DIR$/forum/utils/diff.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/100108_upgrade_ef.sql" afterPath="$PROJECT_DIR$/sql_scripts/100108_upgrade_ef.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.pydevproject" afterPath="$PROJECT_DIR$/.pydevproject" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/facebookauth/templates/button.html" afterPath="$PROJECT_DIR$/forum_modules/facebookauth/templates/button.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/expander-arrow-hide.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/expander-arrow-hide.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/feeds/rss_description.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/feeds/rss_description.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_01_25_001.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_01_25_001.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/wordpress.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/wordpress.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/admin.py" afterPath="$PROJECT_DIR$/forum/views/admin.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/oauthauth/consumer.py" afterPath="$PROJECT_DIR$/forum_modules/oauthauth/consumer.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/cache/README.TXT" afterPath="$PROJECT_DIR$/cache/README.TXT" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/copyright/profiles_settings.xml" afterPath="$PROJECT_DIR$/.idea/copyright/profiles_settings.xml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/openid.css" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/openid.css" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/badges.sql" afterPath="$PROJECT_DIR$/sql_scripts/badges.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/paginator.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/paginator.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/cron/README" afterPath="$PROJECT_DIR$/cron/README" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/html.py" afterPath="$PROJECT_DIR$/forum/utils/html.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/facebookauth/urls.py" afterPath="$PROJECT_DIR$/forum_modules/facebookauth/urls.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/badge.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/badge.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/localauth/forms.py" afterPath="$PROJECT_DIR$/forum_modules/localauth/forms.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/notarobot.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/notarobot.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_02_26_001.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_02_26_001.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/091208_upgrade_evgeny_1.sql" afterPath="$PROJECT_DIR$/sql_scripts/091208_upgrade_evgeny_1.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/cnprog_new_install_2009_02_28.sql" afterPath="$PROJECT_DIR$/sql_scripts/cnprog_new_install_2009_02_28.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.form.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.form.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_tabs.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_tabs.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.gitignore" afterPath="$PROJECT_DIR$/.gitignore" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_12_24_001.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_12_24_001.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/dot-list.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/dot-list.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/oauthauth/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/oauthauth/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/facebook.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/facebook.gif" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/blogger.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/blogger.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_12_27_002.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_12_27_002.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/answer_edit.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/answer_edit.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/oauthauth/lib/oauth.py" afterPath="$PROJECT_DIR$/forum_modules/oauthauth/lib/oauth.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/recaptcha/settings.py" afterPath="$PROJECT_DIR$/forum_modules/recaptcha/settings.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.post.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.post.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_responses.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_responses.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/jquery.openid.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/jquery.openid.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/feedback.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/feedback.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.admin.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.admin.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/cnprog_new_install_2009_04_07.sql" afterPath="$PROJECT_DIR$/sql_scripts/cnprog_new_install_2009_04_07.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/post_contributor_info.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/post_contributor_info.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/091111_upgrade_evgeny.sql" afterPath="$PROJECT_DIR$/sql_scripts/091111_upgrade_evgeny.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/404.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/404.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/wmd-test.html" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/wmd/wmd-test.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/index_.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/index_.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/vcs.xml" afterPath="$PROJECT_DIR$/.idea/vcs.xml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/INSTALL.webfaction" afterPath="$PROJECT_DIR$/INSTALL.webfaction" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/project_badges/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/project_badges/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/models/__init__.py" afterPath="$PROJECT_DIR$/forum/models/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/settings.py" afterPath="$PROJECT_DIR$/settings.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/excanvas.pack.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/excanvas.pack.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/openidauth/settings.py" afterPath="$PROJECT_DIR$/forum_modules/openidauth/settings.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/about.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/about.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/drop-all-tables.sh" afterPath="$PROJECT_DIR$/sql_scripts/drop-all-tables.sh" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/cron/once_award_badges" afterPath="$PROJECT_DIR$/cron/once_award_badges" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/sphinxfulltext/settings.py" afterPath="$PROJECT_DIR$/forum_modules/sphinxfulltext/settings.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/management/commands/__init__.py" afterPath="$PROJECT_DIR$/forum/management/commands/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/question_list.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/question_list.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/README" afterPath="$PROJECT_DIR$/forum/skins/README" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/templatetags/__init__.py" afterPath="$PROJECT_DIR$/forum/templatetags/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_email_subscriptions.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_email_subscriptions.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/openid/vidoop.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/openid/vidoop.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/localauth/templates/loginform.html" afterPath="$PROJECT_DIR$/forum_modules/localauth/templates/loginform.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/locale/en/LC_MESSAGES/django.mo" afterPath="$PROJECT_DIR$/locale/en/LC_MESSAGES/django.mo" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/cnprog.xml" afterPath="$PROJECT_DIR$/sql_scripts/cnprog.xml" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/middleware/cancel.py" afterPath="$PROJECT_DIR$/forum/middleware/cancel.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/books/models.py" afterPath="$PROJECT_DIR$/forum_modules/books/models.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/cron/multi_award_badges" afterPath="$PROJECT_DIR$/cron/multi_award_badges" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/LICENSE" afterPath="$PROJECT_DIR$/LICENSE" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/update_2009_04_10_001.sql" afterPath="$PROJECT_DIR$/sql_scripts/update_2009_04_10_001.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/oauthauth/settings.py" afterPath="$PROJECT_DIR$/forum_modules/oauthauth/settings.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/jquery-1.2.6.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/jquery-1.2.6.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/rmpyc" afterPath="$PROJECT_DIR$/rmpyc" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_reputation.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_reputation.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/admin.py" afterPath="$PROJECT_DIR$/forum/admin.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/views/meta.py" afterPath="$PROJECT_DIR$/forum/views/meta.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/openidico.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/openidico.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/templatetags/smart_if.py" afterPath="$PROJECT_DIR$/forum/templatetags/smart_if.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/vote-accepted.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/vote-accepted.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/logout.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/logout.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/manage.py" afterPath="$PROJECT_DIR$/manage.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/urls.py" afterPath="$PROJECT_DIR$/forum/urls.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/edit_user_email_feeds_form.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/edit_user_email_feeds_form.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/feeds/rss_title.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/feeds/rss_title.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/header.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/header.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/gray-up-arrow-h18px.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/gray-up-arrow-h18px.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/wordpress.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/wordpress.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/openidauth/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/openidauth/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/badges/base.py" afterPath="$PROJECT_DIR$/forum/badges/base.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.openid.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.openid.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.project" afterPath="$PROJECT_DIR$/.project" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/sql_scripts/cnprog_new_install_2009_04_09.sql" afterPath="$PROJECT_DIR$/sql_scripts/cnprog_new_install_2009_04_09.sql" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/templatetags/extra_filters.py" afterPath="$PROJECT_DIR$/forum/templatetags/extra_filters.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/WISH_LIST" afterPath="$PROJECT_DIR$/WISH_LIST" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/myopenid.ico" afterPath="$PROJECT_DIR$/forum/skins/default/media/jquery-openid/images/myopenid.ico" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.flot.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.flot.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/base_content.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/base_content.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/ROADMAP.rst" afterPath="$PROJECT_DIR$/ROADMAP.rst" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/auth/signin.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/auth/signin.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/settings/base.py" afterPath="$PROJECT_DIR$/forum/settings/base.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/feed.py" afterPath="$PROJECT_DIR$/forum/feed.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/cache.py" afterPath="$PROJECT_DIR$/forum/utils/cache.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/auth/auth_settings.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/auth/auth_settings.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_info.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_info.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.editor.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/com.cnprog.editor.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/robotstxt/__init__.py" afterPath="$PROJECT_DIR$/forum_modules/robotstxt/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.ajaxfileupload.js" afterPath="$PROJECT_DIR$/forum/skins/default/media/js/jquery.ajaxfileupload.js" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum_modules/robotstxt/urls.py" afterPath="$PROJECT_DIR$/forum_modules/robotstxt/urls.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/middleware/pagesize.py" afterPath="$PROJECT_DIR$/forum/middleware/pagesize.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/revisions_question.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/revisions_question.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/context.py" afterPath="$PROJECT_DIR$/forum/context.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/close-small-dark.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/close-small-dark.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/templates/user_recent.html" afterPath="$PROJECT_DIR$/forum/skins/default/templates/user_recent.html" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/cc-wiki.png" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/cc-wiki.png" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/utils/__init__.py" afterPath="$PROJECT_DIR$/forum/utils/__init__.py" />
- <change type="MODIFICATION" beforePath="$PROJECT_DIR$/forum/skins/default/media/images/box-arrow.gif" afterPath="$PROJECT_DIR$/forum/skins/default/media/images/box-arrow.gif" />
- </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="urls.py" pinned="false" current="false" current-in-tab="false">
- <entry file="file://$PROJECT_DIR$/forum/urls.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
- <folding />
- </state>
- </provider>
- </entry>
- </file>
- <file leaf-file-name="authentication.py" pinned="false" current="false" current-in-tab="false">
- <entry file="file://$PROJECT_DIR$/forum_modules/facebookauth/authentication.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="49" column="42" selection-start="1881" selection-end="1881" vertical-scroll-proportion="0.0">
- <folding />
- </state>
- </provider>
- </entry>
- </file>
- <file leaf-file-name="consumer.py" pinned="false" current="false" current-in-tab="false">
- <entry file="file://$PROJECT_DIR$/forum_modules/oauthauth/consumer.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0">
- <folding />
- </state>
- </provider>
- </entry>
- </file>
- <file leaf-file-name="authentication.py" pinned="false" current="false" current-in-tab="false">
- <entry file="file://$PROJECT_DIR$/forum_modules/oauthauth/authentication.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="15" column="53" selection-start="477" selection-end="477" vertical-scroll-proportion="0.0">
- <folding />
- </state>
- </provider>
- </entry>
- </file>
- <file leaf-file-name="__init__.py" pinned="false" current="false" current-in-tab="false">
- <entry file="file://$PROJECT_DIR$/forum/authentication/__init__.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="10" column="33" selection-start="335" selection-end="335" vertical-scroll-proportion="0.0">
- <folding />
- </state>
- </provider>
- </entry>
- </file>
- <file leaf-file-name="badges.py" pinned="false" current="false" current-in-tab="false">
- <entry file="file://$PROJECT_DIR$/forum_modules/default_badges/badges.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="222" column="36" selection-start="8774" selection-end="8774" vertical-scroll-proportion="0.0">
- <folding />
- </state>
- </provider>
- </entry>
- </file>
- <file leaf-file-name="readers.py" pinned="false" current="true" current-in-tab="true">
- <entry file="file://$PROJECT_DIR$/forum/views/readers.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="446" column="24" selection-start="18616" selection-end="18616" vertical-scroll-proportion="0.026397515">
- <folding />
- </state>
- </provider>
- </entry>
- </file>
- </leaf>
- </component>
- <component name="FindManager">
- <FindUsagesManager>
- <setting name="OPEN_NEW_TAB" value="false" />
- </FindUsagesManager>
- </component>
- <component name="IdeDocumentHistory">
- <option name="changedFiles">
- <list>
- <option value="$PROJECT_DIR$/settings_local.py.dist" />
- <option value="$PROJECT_DIR$/forum_modules/recaptcha/settings.py" />
- <option value="$PROJECT_DIR$/settings.py" />
- <option value="$PROJECT_DIR$/forum_modules/openidauth/__init__.py" />
- <option value="$PROJECT_DIR$/forum_modules/sphinxfulltext/README.txt" />
- <option value="$PROJECT_DIR$/INSTALL" />
- <option value="$PROJECT_DIR$/settings_local.py" />
- <option value="$PROJECT_DIR$/forum_modules/facebookauth/authentication.py" />
- <option value="$PROJECT_DIR$/forum_modules/oauthauth/authentication.py" />
- <option value="$PROJECT_DIR$/forum/authentication/__init__.py" />
- <option value="$PROJECT_DIR$/forum_modules/project_badges/settings.py" />
- <option value="$PROJECT_DIR$/forum_modules/default_badges/badges.py" />
- <option value="$PROJECT_DIR$/forum_modules/project_badges/badges.py" />
- <option value="$PROJECT_DIR$/forum/views/readers.py" />
- <option value="$PROJECT_DIR$/forum/skins/default/templates/question.html" />
- <option value="$PROJECT_DIR$/forum/forms.py" />
- </list>
- </option>
- </component>
- <component name="ModuleEditorState">
- <option name="LAST_EDITED_MODULE_NAME" />
- <option name="LAST_EDITED_TAB_NAME" />
- </component>
- <component name="ProjectInspectionProfilesVisibleTreeState">
- <entry key="Project Default">
- <profile-state />
- </entry>
- </component>
- <component name="ProjectLevelVcsManager">
- <OptionsSetting value="true" id="Add" />
- <OptionsSetting value="true" id="Remove" />
- <OptionsSetting value="true" id="Checkout" />
- <OptionsSetting value="true" id="Update" />
- <OptionsSetting value="true" id="Status" />
- <OptionsSetting value="true" id="Edit" />
- <OptionsSetting value="true" id="Undo Check Out" />
- <OptionsSetting value="true" id="Get Latest Version" />
- <ConfirmationsSetting value="1" id="Add" />
- <ConfirmationsSetting value="0" id="Remove" />
- </component>
- <component name="ProjectReloadState">
- <option name="STATE" value="0" />
- </component>
- <component name="ProjectView">
- <navigator currentView="ProjectPane" proportions="" version="1" splitterProportion="0.5">
- <flattenPackages />
- <showMembers />
- <showModules />
- <showLibraryContents />
- <hideEmptyPackages />
- <abbreviatePackageNames />
- <autoscrollToSource />
- <autoscrollFromSource />
- <sortByType />
- </navigator>
- <panes>
- <pane id="PackagesPane" />
- <pane id="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="forum_modules" />
- <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_modules" />
- <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
- </PATH_ELEMENT>
- <PATH_ELEMENT>
- <option name="myItemId" value="project_badges" />
- <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_modules" />
- <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
- </PATH_ELEMENT>
- <PATH_ELEMENT>
- <option name="myItemId" value="default_badges" />
- <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="views" />
- <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
- </PATH_ELEMENT>
- </PATH>
- </subPane>
- </pane>
- <pane id="Scope" />
- <pane id="Favorites" />
- </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="project.propVCSSupport.Mappings" />
- <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="RecentsManager">
- <key name="CopyFile.RECENT_KEYS">
- <recent name="C:\osqadev\100302\osqa\forum_modules\dzoneauth\templates" />
- <recent name="C:\osqadev\100302\osqa\forum\skins" />
- <recent name="C:\osqadev\100302\osqa\cache" />
- <recent name="C:\osqadev\100302\osqa" />
- </key>
- <key name="MoveFile.RECENT_KEYS">
- <recent name="C:\osqadev\100302\osqa" />
- <recent name="C:\osqadev\100302\osqa\forum" />
- <recent name="C:\osqadev\100302\osqa\forum\skins\default\templates\auth" />
- </key>
- </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="JavascriptDebugSession" factoryName="Local">
- <JSDebuggerConfigurationSettings>
- <option name="engineId" value="embedded" />
- <option name="fileUrl" />
- </JSDebuggerConfigurationSettings>
- <method>
- <option name="AntTarget" enabled="false" />
- <option name="BuildArtifacts" enabled="false" />
- <option name="Maven.BeforeRunTask" enabled="false" />
- </method>
- </configuration>
- <configuration default="true" type="JUnit" factoryName="JUnit">
- <extension name="coverage" enabled="false" merge="false" />
- <extension name="snapshooter" />
- <module name="" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
- <option name="ALTERNATIVE_JRE_PATH" />
- <option name="PACKAGE_NAME" />
- <option name="MAIN_CLASS_NAME" />
- <option name="METHOD_NAME" />
- <option name="TEST_OBJECT" value="class" />
- <option name="VM_PARAMETERS" />
- <option name="PARAMETERS" />
- <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
- <option name="ENV_VARIABLES" />
- <option name="PASS_PARENT_ENVS" value="true" />
- <option name="TEST_SEARCH_SCOPE">
- <value defaultName="moduleWithDependencies" />
- </option>
- <envs />
- <method>
- <option name="AntTarget" enabled="false" />
- <option name="BuildArtifacts" enabled="false" />
- <option name="Make" enabled="true" />
- <option name="Maven.BeforeRunTask" enabled="false" />
- </method>
- </configuration>
- <list size="0" />
- <configuration name="<template>" type="WebApp" default="true" selected="false">
- <Host>localhost</Host>
- <Port>5050</Port>
- </configuration>
- </component>
- <component name="ShelveChangesManager" show_recycled="false" />
- <component name="StarteamConfiguration">
- <option name="SERVER" value="" />
- <option name="PORT" value="49201" />
- <option name="USER" value="" />
- <option name="PASSWORD" value="" />
- <option name="PROJECT" value="" />
- <option name="VIEW" value="" />
- <option name="ALTERNATIVE_WORKING_PATH" value="" />
- <option name="LOCK_ON_CHECKOUT" value="false" />
- <option name="UNLOCK_ON_CHECKIN" value="false" />
- </component>
- <component name="SvnConfiguration">
- <option name="USER" value="" />
- <option name="PASSWORD" value="" />
- <option name="LAST_MERGED_REVISION" />
- <option name="UPDATE_RUN_STATUS" value="false" />
- <option name="MERGE_DRY_RUN" value="false" />
- <option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
- <option name="UPDATE_LOCK_ON_DEMAND" value="false" />
- <option name="IGNORE_SPACES_IN_MERGE" value="false" />
- <option name="DETECT_NESTED_COPIES" value="false" />
- <option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
- <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
- <configuration useDefault="true">F:\Users\COOL\AppData\Roaming\Subversion</configuration>
- <myIsUseDefaultProxy>false</myIsUseDefaultProxy>
- <supportedVersion>125</supportedVersion>
- </component>
- <component name="TaskManager">
- <task active="true" id="Default" summary="Default task">
- <changelist id="df1b69cf-9c35-4a78-9890-c59b542399e8" name="Default" comment="" />
- <created>1266447811437</created>
- <updated>1266447811437</updated>
- </task>
- <servers />
- </component>
- <component name="TodoView" selected-index="0">
- <todo-panel id="selected-file">
- <are-packages-shown value="false" />
- <are-modules-shown value="false" />
- <flatten-packages value="false" />
- <is-autoscroll-to-source value="true" />
- </todo-panel>
- <todo-panel id="all">
- <are-packages-shown value="true" />
- <are-modules-shown value="false" />
- <flatten-packages value="false" />
- <is-autoscroll-to-source value="true" />
- </todo-panel>
- <todo-panel id="default-changelist">
- <are-packages-shown value="false" />
- <are-modules-shown value="false" />
- <flatten-packages value="false" />
- <is-autoscroll-to-source value="false" />
- </todo-panel>
- </component>
- <component name="ToolWindowManager">
- <frame x="1277" y="0" width="1286" height="806" extended-state="0" />
- <editor active="true" />
- <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="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.329927" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
- <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="true" content_ui="tabs" />
- <window_info id="Maven Projects" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="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="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.32981315" sideWeight="0.0" 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="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="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="ENABLE_BACKGROUND_PROCESSES" value="true" />
- <option name="CHANGED_ON_SERVER_INTERVAL" value="60" />
- <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$/settings_local.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="82" column="52" selection-start="2156" selection-end="2156" vertical-scroll-proportion="0.0" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum/skins/default/templates/header.html">
- <provider selected="true" editor-type-id="text-editor">
- <state line="7" column="20" selection-start="198" selection-end="356" vertical-scroll-proportion="-4.9583335" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum_modules/facebookauth/settings.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum_modules/default_badges/settings.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="2" column="9" selection-start="0" selection-end="308" vertical-scroll-proportion="0.0" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum_modules/project_badges/badges.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="8" column="68" selection-start="328" selection-end="328" vertical-scroll-proportion="0.211838" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum_modules/project_badges/__init__.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum_modules/project_badges/settings.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="3" column="54" selection-start="133" selection-end="133" vertical-scroll-proportion="0.077389985" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum/urls.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum_modules/facebookauth/authentication.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="49" column="42" selection-start="1881" selection-end="1881" vertical-scroll-proportion="0.0" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum_modules/oauthauth/consumer.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="0" column="0" selection-start="0" selection-end="0" vertical-scroll-proportion="0.0" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum_modules/oauthauth/authentication.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="15" column="53" selection-start="477" selection-end="477" vertical-scroll-proportion="0.0" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum/authentication/__init__.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="10" column="33" selection-start="335" selection-end="335" vertical-scroll-proportion="0.0" />
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum_modules/default_badges/badges.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="222" column="36" selection-start="8774" selection-end="8774" vertical-scroll-proportion="0.0">
- <folding />
- </state>
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum/skins/default/templates/question.html">
- <provider selected="true" editor-type-id="text-editor">
- <state line="471" column="112" selection-start="31200" selection-end="31200" vertical-scroll-proportion="0.028192371">
- <folding />
- </state>
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum/forms.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="114" column="33" selection-start="4668" selection-end="4668" vertical-scroll-proportion="0.32225913">
- <folding />
- </state>
- </provider>
- </entry>
- <entry file="file://$PROJECT_DIR$/forum/views/readers.py">
- <provider selected="true" editor-type-id="text-editor">
- <state line="446" column="24" selection-start="18616" selection-end="18616" vertical-scroll-proportion="0.026397515">
- <folding />
- </state>
- </provider>
- </entry>
- </component>
-</project>
-
@@ -56,7 +56,7 @@ Notice that you will need to register with recaptcha.net and receive recaptcha public and private keys that need to be saved in your settings_local.py file -NOTES: django_authopenid is included into OSQA code +NOTES: django_authopenid is included into OSQA code and is significantly modified. http://code.google.com/p/django-authopenid/ no need to install this library @@ -66,7 +66,7 @@ B. INSTALLATION make osqa installation server-readable on Linux command might be: chown -R yourlogin:apache /path/to/OSQA - + directories templates/upfiles and log must be server writable on Linux type chmod @@ -77,28 +77,28 @@ B. INSTALLATION 1. Settings file -Copy settings_local.py.dist to settings_local.py and -update all your settings. Check settings.py and update +Copy settings_local.py.dist to settings_local.py and +update all your settings. Check settings.py and update it as well if necessory. Section C explains configuration paramaters. 2. Database -Prepare your database by using the same database/account +Prepare your database by using the same database/account configuration from above. e.g, create database osqa DEFAULT CHARACTER SET UTF8 COLLATE utf8_general_ci; grant all on osqa.* to 'osqa'@'localhost'; And then run "python manage.py syncdb" to synchronize your database. -3. Running OSQA on the development server +3. Running OSQA on the development server -Run "python manage.py runserver" to startup django +Run "python manage.py runserver" to startup django development environment. (Under Linux you can use command "python manage.py runserver `hostname -i`:8000", where you can use any other available number for the port) -you might want to have DEBUG=True in the beginning of settings.py +you might want to have DEBUG=True in the beginning of settings.py when using the test server 4. Installation under Apache/WSGI @@ -118,7 +118,7 @@ import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler() ----------- -insert method is used for path because if the forum directory name +insert method is used for path because if the forum directory name is by accident the same as some other python module you wull see strange errors - forum won't be found even though it's in the python path. for example using name "test" is @@ -133,7 +133,7 @@ WSGISocketPrefix /path/to/socket/sock #must be readable and writable by apache WSGIPythonHome /usr/local #must be readable by apache WSGIPythonEggs /var/python/eggs #must be readable and writable by apache -#NOTE: all urs below will need to be adjusted if +#NOTE: all urs below will need to be adjusted if #settings.FORUM_SCRIPT_ALIAS !='' (e.g. = 'forum/') #this allows "rooting" forum at http://example.com/forum, if you like <VirtualHost ...your ip...:80> @@ -144,8 +144,8 @@ WSGIPythonEggs /var/python/eggs #must be readable and writable by apache #run mod_wsgi process for django in daemon mode #this allows avoiding confused timezone settings when #another application runs in the same virtual host - WSGIDaemonProcess OSQA - WSGIProcessGroup OSQA + WSGIDaemonProcess OSQA + WSGIProcessGroup OSQA #force all content to be served as static files #otherwise django will be crunching images through itself wasting time @@ -213,42 +213,42 @@ WSGIPythonEggs /var/python/eggs #must be readable and writable by apache in settings_local.py set USE_SPHINX_SEARCH=True - adjust other settings that have SPHINX_* prefix accordingly + adjust other settings that have SPHINX_* prefix accordingly remember that there must be trailing comma in parentheses for SHPINX_SEARCH_INDICES tuple - particlarly with just one item! - + in settings.py look for INSTALLED_APPS and uncomment #'djangosphinx', - + 6. Email subscriptions - + This function at the moment requires Django 1.1 edit paths in the file cron/send_email_alerts set up a cron job to call cron/send_email_alerts once or twice a day - subscription sender may be tested manually in shell + subscription sender may be tested manually in shell by calling cron/send_email_alerts 7. Sitemap Sitemap will be available at /<settings_local.FORUM_SCRIPT_ALIAS>sitemap.xml e.g yoursite.com/forum/sitemap.xml -google will be pinged each time question, answer or +google will be pinged each time question, answer or comment is saved or a question deleted -for this to be useful - do register you sitemap with Google at +for this to be useful - do register you sitemap with Google at https://www.google.com/webmasters/tools/ 8. Miscellaneous -There are some demo scripts under sql_scripts folder, -including badges and test accounts for CNProg.com. You +There are some demo scripts under sql_scripts folder, +including badges and test accounts for CNProg.com. You don't need them to run your sample. C. CONFIGURATION PARAMETERS -#the only parameter that needs to be touched in settings.py is +#the only parameter that needs to be touched in settings.py is DEBUG=False #set to True to enable debug mode #all forum parameters are set in file settings_local.py @@ -269,7 +269,7 @@ EMAIL_USE_TLS=False TIME_ZONE = 'America/Tijuana' APP_TITLE = u'OSQA Q&A Forum' #title of your forum APP_KEYWORDS = u'OSQA,forum,community' #keywords for search engines -APP_DESCRIPTION = u'Ask and answer questions.' #site description for searche engines +APP_DESCRIPTION = u'Ask and answer questions.' #site description for searche engines APP_INTRO = u'<p>Ask and answer questions, make the world better!</p>' #slogan that goes to front page in logged out mode APP_COPYRIGHT = '' #copyright message diff --git a/INSTALL.pip b/INSTALL.pip new file mode 100644 index 00000000..92b1c7fa --- /dev/null +++ b/INSTALL.pip @@ -0,0 +1,31 @@ +* Install virtualenv and pip: + easy_install virtualenv + easy_install pip + +* Install MySQL: + sudo apt-get install mysql-client mysql-server + +* Install sphinxsearch. See: + [optional] + http://sphinxsearch.com/downloads.html + http://www.hackido.com/2009/01/install-sphinx-search-on-ubuntu.html + git://github.com/johnl/deb-sphinx-search.git + +* Install a virtual environment OUTSIDE of this directory: + pip install -E ~/env -r osqa-requirements.txt +[there is discussion on the pinax forums about what it should be outside +the source directory] + +* Notice that you will need to register with recaptcha.net and receive + recaptcha public and private keys that need to be saved in your + settings_local.py file + +* Start your environment: + source ~/env/bin/activate + +* Install mysql-python into your virtualenv, because we can't +automagically install it with pip: + easy_install --prefix ~/env/ mysql-python + +For more information about why pip can't automatically install the +MySQL driver, see this message: http://groups.google.com/group/python-virtualenv/msg/ea988085951c92b3 diff --git a/INSTALL.webfaction b/INSTALL.webfaction new file mode 100644 index 00000000..401971a0 --- /dev/null +++ b/INSTALL.webfaction @@ -0,0 +1,346 @@ +Detailed instructions for installing OSQA on WebFaction + +Adapted from http://code.pinaxproject.com/wiki/DetailedPinaxWebfaction/ + +Please email turian at gmail with any updates or corrections. + + +Installing OSQA on Webfaction +------------------------------------ + +Details the steps for setting up OSQA on a Webfaction shared-hosting +account, including email setup, using Apache2, mod_wsgi, psycopg2. + +If you want to search-and-replace through this file, you will need to replace: + osqa_server [name of Webfaction application, which will be in ~/webapps/] + osqa_static [name of Webfaction application for static media serving] + DOMAIN.com [domain name for OSQA site] + PORT [port number assigned by WebFaction to your mod_wsgi application] + SITENAME [name you give the OSQA site, which will contain the apache logs] + MYOSQA [name of the OSQA project] + MAILBOX_USERNAME [username you give the email address] + MAILBOX_PASSWORD [password that webfaction gives to this email username] + OSQADATABASE_NAME [username you give the database] + OSQADATABASE_PASSWORD [password that webfaction gives to this database] + ~/envs/osqa [directory for the OSQA python environment, grep for 'env'] + USERNAME [your WebFaction username] + +Some things I'm not sure about: + +Here's what I don't know how to do: + * Set up a nginx server for static media. + * Configure sphinx search + * Use PostgreSQL, not MySQL: http://osqa.net/question/13/can-i-use-osqa-with-postgresql + + +Webfaction Control Panel +-------------------------- + +(Note: if you sign up and pick django it will create the application +for you, website/subdomain and associate the two for you.) + + If necessary, add or create any domains or subdomains you may need. + + https://panel.webfaction.com/domain/list/ + + Let's call the domain DOMAIN.com. + + Create a new Webfaction application with a "Type:" of "mod_wsgi + 2.5/Python2.5", naming it "osqa_server". (These instructions + might also work with mod_wsgi 2.0, if you like.) + + https://panel.webfaction.com/app_/list + + Note the port number assigned to the mod_wsgi application. Call + it PORT. + + Create a new Webfaction website which will associate the subdomain + with the new osqa_server application. Give it name SITENAME, at least one + domain, and set it to use the osqa_server application for the site's + root location, "/". + + https://panel.webfaction.com/site/list + + You will need to create a database, typically one for each project + you create. Change the type to PostgreSql and modify the name (it + defaults to your webfaction account name) by adding an underscore + and a project-specific identifier such as "_osqa". Before + leaving this section of the control panel, you may wish to change + the password. + + https://panel.webfaction.com/database/create + + Call these OSQADATABASE_NAME and OSQADATABASE_PASSWORD. + + Save the database password for later. + + [The following I haven't figured out yet] + You will probably want to add a static media server. This is a + Webfaction application. I created one of type "Static only (no + .htaccess)" and with the name of "osqa_static". + + https://panel.webfaction.com/app_/create + + To configure email, you need an email mailbox. Add one here. Note + that your mailbox password shouldn't be the same password you use + to SSH to webfaction. + + https://panel.webfaction.com/mailbox/list + + Save the mail password for later. + We will call the username and password MAILBOX_USERNAME and + MAILBOX_PASSWORD, respectively. + You might also consider adding an email address like admin@DOMAIN.com, + here: + + https://panel.webfaction.com/email/list + + +OSQA Software +-------------- + + Log onto webfaction and get the code. I use my fork because I have + a simple pip installation: + git://github.com/turian/osqa.git + In my situation, I keep source code in ~/utils/src, create + virtual environments in ~/envs/osqa, and create Pinax projects in + ~/webapps/osqa_server/projects. + + You will need pip + virtualenv installed: + + easy_install --prefix=~/utils/ pip + easy_install --prefix=~/utils/ virtualenv + + cd ~/utils/src/ + git clone git://github.com/turian/osqa.git + cd osqa + + # We need python2.5 to be compatible with WSGI + python2.5 ~/utils/bin/pip install -E ~/envs/osqa -r osqa-requirements.txt + source ~/envs/osqa/bin/activate + + # [Optional] If you want a MySQL database + easy_install-2.5 --prefix ~/envs/osqa/ mysql-python + +Additional Software +------------------- + + [Note that PostgreSQL installation doesn't work for me.] + + You will need to install psycopg2 separately for PostgreSQL. + Psycopg2 requires a little fiddling. Continuing to + work in the ~/utils/src/ directory: + + cd ~/utils/src/ + wget http://initd.org/pub/software/psycopg/psycopg2-2.0.13.tar.gz + tar zxf psycopg2-2.0.13.tar.gz + cd psycopg2-2.0.13 + nano setup.cfg + + # edit the line reading "#pg_config=" so that it reads: + "pg_config=/usr/local/pgsql/bin/pg_config" + + python2.5 setup.py build + python2.5 setup.py install + + +Create a Project +---------------- + + In Pinax, you clone a project from OSQA. + However, OSQA we just copy it. + + cd ~/webapps/osqa_server + mkdir projects + cd projects + cp -R ~/utils/src/osqa MYOSQA + cd MYOSQA + export OSQAPROJECT=`pwd` + + Make some directories, as described in the OSQA INSTALL file: + [okay I haven't actually done this yet] + +# mkdir -p $OSQASITE/upfiles/ +# mkdir -p $OSQALOG +# sudo chown -R `whoami`:www-data $OSQASITE +# sudo chown -R `whoami`:www-data $OSQALOG +# chmod -R g+w $OSQASITE/upfiles +# chmod -R g+w $OSQALOG + + + Edit the settings files: + + cd $OSQAPROJECT + cp settings_local.py.dist settings_local.py + vi settings_local.py settings.py + + Pay attention to the following settings: + + DATABASE_ENGINE = 'mysql' + DATABASE_NAME = 'OSQADATABASE_NAME' + DATABASE_USER = 'OSQADATABASE_NAME' + DATABASE_PASSWORD = 'OSQADATABASE_PASSWORD' + + EMAIL_HOST='smtp.webfaction.com' + EMAIL_HOST_USER='MAILBOX_USERNAME' + EMAIL_HOST_PASSWORD='MAILBOX_PASSWORD' + EMAIL_PORT='25' + DEFAULT_FROM_EMAIL = 'MAILBOX_USERNAME@DOMAIN.com' + SERVER_EMAIL = 'MAILBOX_USERNAME@DOMAIN.com' + # The following setting might not be necessary, it's used in Pinax tho + CONTACT_EMAIL = "MAILBOX_USERNAME@DOMAIN.com" + + APP_URL = 'http://DOMAIN.com' #used by email notif system and RSS + + [Later on, the install instructions should talk about] + SERVE_MEDIA = False # [Not present, not ready yet] + + Create a directory for logs: + + cd $OSQAPROJECT + mkdir log + + Modify mail cron scripts "cron/send_email_alerts" as follows: + [Pinax has cron/emit_notices.sh, cron/retry_deferred.sh, + cron/send_mail.sh, are these also necessary?] + + #!/bin/sh + + WORKON_HOME=~/envs/osqa + PROJECT_ROOT=~/webapps/osqa_server/projects/MYOSQA/ + + # activate virtual environment + . $WORKON_HOME/bin/activate + + cd $PROJECT_ROOT + python manage.py send_email_alerts >> $PROJECT_ROOT/log/cron_mail.log 2>&1 + + Use command "crontab -e" to add this script to your cron file, to run twice a day:: + + 1 0,12 * * * ~/webapps/osqa_server/projects/MYOSQA/cron/send_email_alerts + + [Configure sphinx] + + Create the database tables, indices, and so forth: + + python manage.py syncdb + + [Ignore the following static media steps, I haven't tried them] + Build media directory links within the project and create symbolic + links on the static media server. + python manage.py build_media -all + mkdir ~/webapps/OSQA_STATIC/MYOSQA + ln -sd ~/webapps/osqa_server/projects/MYOSQA/site_media ~/webapps/OSQA_STATIC/MYOSQA/site_media + + + Set up the badges: + + 1. You should run the SQL commands in: + + sql_scripts/badges.sql + + 2. Edit paths in the file `cron/multi_award_badges`. (This + file doesn't yet exist in the git repositories, so just + copy `cron/send_email_alerts` and make sure the command + `multi_award_badges` is executed.) + + 3. Run `cron/multi_award_badges` to make sure it works okay. + + 4. Use `crontab -e` to call `cron/multi_award_badges` maybe + four times an hour. + + 4,19,34,49 * * * * ~/webapps/osqa_server/projects/MYOSQA/cron/multi_award_badges + + 5. Repeat steps 1-4 for `cron/once_award_badges`. + + +Configure Apache2 +---------------- + + Edit ~/webapps/osqa_server/apache2/conf/httpd.conf as follows:: + + ServerAdmin "MAILBOX_USERNAME@DOMAIN.com" + ServerRoot "/home/USERNAME/webapps/osqa_server/apache2" + ServerName DOMAIN.com + + LoadModule dir_module modules/mod_dir.so + LoadModule env_module modules/mod_env.so + #LoadModule setenvif_module modules/mod_setenvif.so + LoadModule log_config_module modules/mod_log_config.so + LoadModule mime_module modules/mod_mime.so + LoadModule rewrite_module modules/mod_rewrite.so + LoadModule wsgi_module modules/mod_wsgi.so + + KeepAlive Off + Listen PORT + LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + CustomLog /home/USERNAME/logs/user/access_osqa_server_log combined + ErrorLog /home/USERNAME/logs/user/error_osqa_server_log + ServerLimit 2 + + #SetEnvIf X-Forwarded-SSL on HTTPS=1 + + WSGIPythonPath /home/USERNAME/envs/osqa/lib/python2.5/site-packages/ + WSGIScriptAlias / /home/USERNAME/webapps/osqa_server/projects/MYOSQA/osqa.wsgi + + LoadModule alias_module modules/mod_alias.so + WSGIDaemonProcess osqaWSGI user=USERNAME group=USERNAME threads=25 python-path=/home/USERNAME/envs/osqa/lib/python2.5/site-packages + WSGIProcessGroup osqaWSGI + + NameVirtualHost 127.0.0.1:PORT + + #ErrorLog "logs/MYOSQA_2009_05_06.log" + SetHandler none + #Alias /site_media /home/USERNAME/webapps/static/MYOSQA/site_media + + #force all content to be served as static files + #otherwise django will be crunching images through itself wasting time + Alias /content/ /home/USERNAME/webapps/osqa_server/projects/MYOSQA/templates/content/ + Alias /forum/admin/media/ /home/turian/envs/osqa/lib/python2.5/site-packages/django/contrib/admin/media/ + #AliasMatch /([^/]*\.css) /home/USERNAME/webapps/osqa_server/projects/MYOSQA/templates/content/style/$1 + <Directory "/home/USERNAME/webapps/osqa_server/projects/MYOSQA/templates/content"> + # Order deny,allow + # Allow from all + </Directory> + + If you want virtual hosts of the admin interface under HTTPS, please + look at OSQA's install file. + + Create osqa.wsgi and edit it: + cp osqa.wsgi.dist osqa.wsgi + + Edit ~/webapps/osqa_server/projects/MYOSQA/deploy/osqa.wsgi as follows:: + + import os + import sys + + # redirect sys.stdout to sys.stderr for bad libraries like geopy that uses + # print statements for optional import exceptions. + sys.stdout = sys.stderr + + from os.path import abspath, dirname, join + from site import addsitedir + + # add the virtual environment site-packages to the path + from site import addsitedir + addsitedir('/home/USERNAME/envs/osqa/lib/python2.5/site-packages') + + sys.path.insert(0, abspath(join(dirname(__file__), "../"))) + sys.path.append(abspath(dirname(__file__))) + + from django.conf import settings + os.environ["DJANGO_SETTINGS_MODULE"] = "MYOSQA.settings" + + #print sys.path + + from django.core.handlers.wsgi import WSGIHandler + application = WSGIHandler() + +And then you're up and running with: + + ~/webapps/osqa_server/apache2/bin/stop + ~/webapps/osqa_server/apache2/bin/start + +You should log in to the admin interface (http://DOMAIN.com/admin/), +and go to "Sites > Sites", and change the domain name that is used in +all emails. diff --git a/PENDING b/PENDING deleted file mode 100644 index 2931303c..00000000 --- a/PENDING +++ /dev/null @@ -1,28 +0,0 @@ -There are two kinds of things that can be done: -refactorings (think of jogging in the morning, going to a spa, well make the code better :) -new features (go to law school, get a job, do something real) -Just a joke - pick yourself a task and work on it. - -==Refactoring== -* validate HTML -* set up loading of default settings from inside the /forum dir -* automatic dependency checking for modules -* propose how to rename directory forum --> osqa - without breaking things and keeping name of the project root - named the same way - osqa - -==New features== -Whoever wants - pick a feature from the WISH_LIST -add it here and start working on it -If you are not starting immediately - leave it on the wishlist :) - -==Notes== -1)after this is done most new suggested features - may be worked on easily since most of them - only require editing view functions and templates - - However, anyone can work on new features anyway - you'll - just have to probably copy-paste your code into - the branch undergoing refactoring which involves - splitting the files. Auto merging across split points - is harder or impossible. @@ -5,4 +5,3 @@ Demo site is http://askbot.org/meta askbot-devel repository is open to anyone who wants to help develop Askbot - just drop us a note Askbot is based on code of CNPROG, originally created by Mike Chen and Sailing Cai and some code written for OSQA - @@ -0,0 +1,16 @@ +note: there is also WISH_LIST. Here is only stuff that will be done soon. + +Cleanups +========== +* remove usage of EXTERNAL_LEGACY_LOGIN +* remove forum_modules and replace them with normal Python imports +* pack dependency apps inside 'forum' directory + +Features +=========== +* new login system, please see http://groups.google.com/group/askbot/browse_thread/thread/1916dfcf666dd56c +* forum admin interface, some badge configuration + +Development environment +========================== +* set up environment for closure developmen @@ -1,5 +1,7 @@ +* smarter debug mode * The wonder bar (integrated the search / ask functionality)
* The authentication system ???
+* allow multiple logins to the same account * allow multiple logins to the same account
* more advanced templating/skinning system
* per-tag email subscriptions
@@ -8,3 +10,46 @@ * drill-down mode for navigation by tags
* improved admin console
* sort out mess with profile - currently we patch django User
+ +* Some functionality should be moved out of the forums app, in the case +that the forum app is restricted only to authenticated users: + + (r'^%s/$' % _('signin/'), 'django_authopenid.views.signin'), + url(r'^%s$' % _('about/'), app.about, name='about'), + url(r'^%s$' % _('faq/'), app.faq, name='faq'), + url(r'^%s$' % _('privacy/'), app.privacy, name='privacy'), + url(r'^%s$' % _('logout/'), app.logout, name='logout'), + url(r'^%s$' % _('feedback/'), app.feedback, name='feedback'), + (r'^%sfb/' % _('account/'), include('fbconnect.urls')), + (r'^%s' % _('account/'), include('django_authopenid.urls')), + +Copied from old todo list: + +There are two kinds of things that can be done: +refactorings (think of jogging in the morning, going to a spa, well make the code better :) +new features (go to law school, get a job, do something real) +Just a joke - pick yourself a task and work on it. + +==Refactoring== +* validate HTML +* set up loading of default settings from inside the /forum dir +* automatic dependency checking for modules +* propose how to rename directory forum --> osqa + without breaking things and keeping name of the project root + named the same way - osqa + +==New features== +Whoever wants - pick a feature from the WISH_LIST +add it here and start working on it +If you are not starting immediately - leave it on the wishlist :) + +==Notes== +1)after this is done most new suggested features + may be worked on easily since most of them + only require editing view functions and templates + + However, anyone can work on new features anyway - you'll + just have to probably copy-paste your code into + the branch undergoing refactoring which involves + splitting the files. Auto merging across split points + is harder or impossible. diff --git a/askbot-requirements.txt b/askbot-requirements.txt new file mode 100644 index 00000000..66a37fbe --- /dev/null +++ b/askbot-requirements.txt @@ -0,0 +1,9 @@ +django>=1.1 # Note: email subscription sender job requires Django 1.1, everything else works with 1.0 +#mysql-python # Can't use with pip, see http://groups.google.com/group/python-virtualenv/msg/ea988085951c92b3 +python-openid +html5lib +markdown2 +git+git://github.com/robhudson/django-debug-toolbar.git +git+git://github.com/dcramer/django-sphinx.git +svn+http://recaptcha-django.googlecode.com/svn/trunk/ +svn+http://recaptcha.googlecode.com/svn/trunk/recaptcha-plugins/python/ @@ -1,14 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
- <component name="FacetManager">
- <facet type="django" name="Django">
- <configuration>
- <option name="rootFolder" value="$MODULE_DIR$" />
- <option name="templatesFolder" value="$MODULE_DIR$/templates" />
- <option name="settingsModule" value="settings.py" />
- </configuration>
- </facet>
- </component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
diff --git a/osqa.wsgi.dist b/askbot.wsgi.dist index c3a269da..c3a269da 100644 --- a/osqa.wsgi.dist +++ b/askbot.wsgi.dist diff --git a/cache/README.TXT b/cache/README.TXT new file mode 100755 index 00000000..54247a82 --- /dev/null +++ b/cache/README.TXT @@ -0,0 +1 @@ +this file is just a placeholder so the empty directory is not ignored by version control
\ No newline at end of file diff --git a/cron/README b/cron/README new file mode 100644 index 00000000..d5573150 --- /dev/null +++ b/cron/README @@ -0,0 +1,5 @@ +this directory contains sample commands to be executed +by cron + +files with names ending "virtuanenv" should work under Python virtualenv system +other files - with standard unix setup diff --git a/cron/multi_award_badges b/cron/multi_award_badges new file mode 100755 index 00000000..b34abd09 --- /dev/null +++ b/cron/multi_award_badges @@ -0,0 +1,5 @@ +#!/bin/sh +PYTHONPATH=/path/to/dir_above_osqa_site +export PYTHONPATH +PROJECT_ROOT=$PYTHONPATH/osqa_site +python manage.py multi_award_badges >> $PROJECT_ROOT/log/cron_badges.log 2>&1 diff --git a/cron/multi_award_badges_virtualenv b/cron/multi_award_badges_virtualenv new file mode 100755 index 00000000..df58155a --- /dev/null +++ b/cron/multi_award_badges_virtualenv @@ -0,0 +1,10 @@ +#!/bin/sh + +WORKON_HOME=~/envs/osqa +PROJECT_ROOT=~/webapps/osqa_server/projects/osqa/ + +# activate virtual environment +. $WORKON_HOME/bin/activate + +cd $PROJECT_ROOT +python manage.py multi_award_badges >> $PROJECT_ROOT/log/cron_badges.log 2>&1 diff --git a/cron/once_award_badges b/cron/once_award_badges new file mode 100755 index 00000000..43ee2249 --- /dev/null +++ b/cron/once_award_badges @@ -0,0 +1,14 @@ +#!/bin/sh +PYTHONPATH=/path/to/dir_above_osqa_site +export PYTHONPATH +PROJECT_ROOT=$PYTHONPATH/osqa_site +python manage.py once_award_badges >> $PROJECT_ROOT/log/cron_badges.log 2>&1 + + +#!/bin/sh +PYTHONPATH=/usr/local/sites/osqa_production +export PYTHONPATH +PROJECT_ROOT=$PYTHONPATH/robofaqs +python $PROJECT_ROOT/manage.py once_award_badges >> $PROJECT_ROOT/log/cron_badges.log 2>&1 +python $PROJECT_ROOT/manage.py multi_award_badges >> $PROJECT_ROOT/log/cron_badges.log 2>&1 +python $PROJECT_ROOT/manage.py send_email_alerts >> $PROJECT_ROOT/log/cron_email.log 2>&1
\ No newline at end of file diff --git a/cron/once_award_badges_virtualenv b/cron/once_award_badges_virtualenv new file mode 100755 index 00000000..12414969 --- /dev/null +++ b/cron/once_award_badges_virtualenv @@ -0,0 +1,10 @@ +#!/bin/sh + +WORKON_HOME=~/envs/osqa +PROJECT_ROOT=~/webapps/osqa_server/projects/osqa/ + +# activate virtual environment +. $WORKON_HOME/bin/activate + +cd $PROJECT_ROOT +python manage.py once_award_badges >> $PROJECT_ROOT/log/cron_badges.log 2>&1 diff --git a/cron/send_email_alerts b/cron/send_email_alerts index 6358b599..cae09606 100644 --- a/cron/send_email_alerts +++ b/cron/send_email_alerts @@ -1,4 +1,5 @@ -PYTHONPATH=/path/to/dir/above/forum +#!/bin/sh +PYTHONPATH=/path/to/dir_above_osqa_site export PYTHONPATH -APP_ROOT=$PYTHONPATH/nmr-forum2 -/path/to/python $APP_ROOT/manage.py send_email_alerts +PROJECT_ROOT=$PYTHONPATH/osqa_site +/path/to/python $PROJECT_ROOT/manage.py send_email_alerts diff --git a/cron/send_email_alerts_virtualenv b/cron/send_email_alerts_virtualenv new file mode 100644 index 00000000..6c9e154d --- /dev/null +++ b/cron/send_email_alerts_virtualenv @@ -0,0 +1,10 @@ +#!/bin/sh + +WORKON_HOME=~/envs/osqa +PROJECT_ROOT=~/webapps/osqa_server/projects/osqa/ + +# activate virtual environment +. $WORKON_HOME/bin/activate + +cd $PROJECT_ROOT +python manage.py send_email_alerts >> $PROJECT_ROOT/log/cron_mail.log 2>&1 diff --git a/forum/__init__.py b/forum/__init__.py index 85cd5d26..85cd5d26 100644..100755 --- a/forum/__init__.py +++ b/forum/__init__.py diff --git a/forum/admin.py b/forum/admin.py index 88643b92..3afa2241 100644..100755 --- a/forum/admin.py +++ b/forum/admin.py @@ -3,7 +3,6 @@ from django.contrib import admin from models import * - class AnonymousQuestionAdmin(admin.ModelAdmin): """AnonymousQuestion admin class""" @@ -55,7 +54,6 @@ class ActivityAdmin(admin.ModelAdmin): #class BookAuthorRssAdmin(admin.ModelAdmin): # """ admin class""" - admin.site.register(Question, QuestionAdmin) admin.site.register(Tag, TagAdmin) admin.site.register(Answer, Answerdmin) diff --git a/forum/auth.py b/forum/auth.py index 3533b9ce..5d6e71c4 100644..100755 --- a/forum/auth.py +++ b/forum/auth.py @@ -5,16 +5,14 @@ The actions a User is authorised to perform are dependent on their reputation and superuser status. """ import datetime -from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext as _ from django.db import transaction from models import Repute from models import Question from models import Answer +from models import mark_offensive, delete_post_or_answer from const import TYPE_REPUTATION import logging -question_type = ContentType.objects.get_for_model(Question) -answer_type = ContentType.objects.get_for_model(Answer) VOTE_UP = 15 FLAG_OFFENSIVE = 15 @@ -198,7 +196,9 @@ def calculate_reputation(origin, offset): return 1 @transaction.commit_on_success -def onFlaggedItem(item, post, user): +def onFlaggedItem(item, post, user, timestamp=None): + if timestamp is None: + timestamp = datetime.datetime.now() item.save() post.offensive_flag_count = post.offensive_flag_count + 1 @@ -209,12 +209,12 @@ def onFlaggedItem(item, post, user): post.author.save() question = post - if ContentType.objects.get_for_model(post) == answer_type: + if isinstance(post, Answer): question = post.question reputation = Repute(user=post.author, negative=int(REPUTATION_RULES['lose_by_flagged']), - question=question, reputed_at=datetime.datetime.now(), + question=question, reputed_at=timestamp, reputation_type=-4, reputation=post.author.reputation) reputation.save() @@ -228,7 +228,7 @@ def onFlaggedItem(item, post, user): reputation = Repute(user=post.author, negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']), question=question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=-6, reputation=post.author.reputation) reputation.save() @@ -241,21 +241,28 @@ def onFlaggedItem(item, post, user): reputation = Repute(user=post.author, negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']), question=question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=-7, reputation=post.author.reputation) reputation.save() post.deleted = True - #post.deleted_at = datetime.datetime.now() + #post.deleted_at = timestamp #post.deleted_by = Admin post.save() - + mark_offensive.send( + sender=post.__class__, + instance=post, + mark_by=user + ) @transaction.commit_on_success -def onAnswerAccept(answer, user): +def onAnswerAccept(answer, user, timestamp=None): + if timestamp is None: + timestamp = datetime.datetime.now() + answer.accepted = True - answer.accepted_at = datetime.datetime.now() + answer.accepted_at = timestamp answer.question.answer_accepted = True answer.save() answer.question.save() @@ -266,7 +273,7 @@ def onAnswerAccept(answer, user): reputation = Repute(user=answer.author, positive=int(REPUTATION_RULES['gain_by_answer_accepted']), question=answer.question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=2, reputation=answer.author.reputation) reputation.save() @@ -277,13 +284,15 @@ def onAnswerAccept(answer, user): reputation = Repute(user=user, positive=int(REPUTATION_RULES['gain_by_accepting_answer']), question=answer.question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=3, reputation=user.reputation) reputation.save() @transaction.commit_on_success -def onAnswerAcceptCanceled(answer, user): +def onAnswerAcceptCanceled(answer, user, timestamp=None): + if timestamp is None: + timestamp = datetime.datetime.now() answer.accepted = False answer.accepted_at = None answer.question.answer_accepted = False @@ -296,7 +305,7 @@ def onAnswerAcceptCanceled(answer, user): reputation = Repute(user=answer.author, negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']), question=answer.question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=-2, reputation=answer.author.reputation) reputation.save() @@ -307,13 +316,15 @@ def onAnswerAcceptCanceled(answer, user): reputation = Repute(user=user, negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']), question=answer.question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=-1, reputation=user.reputation) reputation.save() @transaction.commit_on_success -def onUpVoted(vote, post, user): +def onUpVoted(vote, post, user, timestamp=None): + if timestamp is None: + timestamp = datetime.datetime.now() vote.save() post.vote_up_count = int(post.vote_up_count) + 1 @@ -322,25 +333,28 @@ def onUpVoted(vote, post, user): if not post.wiki: author = post.author - if Repute.objects.get_reputation_by_upvoted_today(author) < int(REPUTATION_RULES['scope_per_day_by_upvotes']): + todays_rep_gain = Repute.objects.get_reputation_by_upvoted_today(author) + if todays_rep_gain < int(REPUTATION_RULES['scope_per_day_by_upvotes']): author.reputation = calculate_reputation(author.reputation, int(REPUTATION_RULES['gain_by_upvoted'])) author.save() question = post - if ContentType.objects.get_for_model(post) == answer_type: + if isinstance(post, Answer): question = post.question reputation = Repute(user=author, positive=int(REPUTATION_RULES['gain_by_upvoted']), question=question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=1, reputation=author.reputation) reputation.save() @transaction.commit_on_success -def onUpVotedCanceled(vote, post, user): +def onUpVotedCanceled(vote, post, user, timestamp=None): + if timestamp is None: + timestamp = datetime.datetime.now() vote.delete() post.vote_up_count = int(post.vote_up_count) - 1 @@ -356,19 +370,21 @@ def onUpVotedCanceled(vote, post, user): author.save() question = post - if ContentType.objects.get_for_model(post) == answer_type: + if isinstance(post, Answer): question = post.question reputation = Repute(user=author, negative=int(REPUTATION_RULES['lose_by_upvote_canceled']), question=question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=-8, reputation=author.reputation) reputation.save() @transaction.commit_on_success -def onDownVoted(vote, post, user): +def onDownVoted(vote, post, user, timestamp=None): + if timestamp is None: + timestamp = datetime.datetime.now() vote.save() post.vote_down_count = int(post.vote_down_count) + 1 @@ -382,13 +398,13 @@ def onDownVoted(vote, post, user): author.save() question = post - if ContentType.objects.get_for_model(post) == answer_type: + if isinstance(post, Answer): question = post.question reputation = Repute(user=author, negative=int(REPUTATION_RULES['lose_by_downvoted']), question=question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=-3, reputation=author.reputation) reputation.save() @@ -400,13 +416,15 @@ def onDownVoted(vote, post, user): reputation = Repute(user=user, negative=int(REPUTATION_RULES['lose_by_downvoting']), question=question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=-5, reputation=user.reputation) reputation.save() @transaction.commit_on_success -def onDownVotedCanceled(vote, post, user): +def onDownVotedCanceled(vote, post, user, timestamp=None): + if timestamp is None: + timestamp = datetime.datetime.now() vote.delete() post.vote_down_count = int(post.vote_down_count) - 1 @@ -422,13 +440,13 @@ def onDownVotedCanceled(vote, post, user): author.save() question = post - if ContentType.objects.get_for_model(post) == answer_type: + if isinstance(post, Answer): question = post.question reputation = Repute(user=author, positive=int(REPUTATION_RULES['gain_by_downvote_canceled']), question=question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=4, reputation=author.reputation) reputation.save() @@ -440,12 +458,13 @@ def onDownVotedCanceled(vote, post, user): reputation = Repute(user=user, positive=int(REPUTATION_RULES['gain_by_canceling_downvote']), question=question, - reputed_at=datetime.datetime.now(), + reputed_at=timestamp, reputation_type=5, reputation=user.reputation) reputation.save() -def onDeleteCanceled(post, user): +#here timestamp is not used, I guess added for consistency +def onDeleteCanceled(post, user, timestamp=None): post.deleted = False post.deleted_by = None post.deleted_at = None @@ -462,10 +481,12 @@ def onDeleteCanceled(post, user): tag.deleted_at = None tag.save() -def onDeleted(post, user): +def onDeleted(post, user, timestamp=None): + if timestamp is None: + timestamp = datetime.datetime.now() post.deleted = True post.deleted_by = user - post.deleted_at = datetime.datetime.now() + post.deleted_at = timestamp post.save() if isinstance(post, Question): @@ -473,7 +494,7 @@ def onDeleted(post, user): if tag.used_count == 1: tag.deleted = True tag.deleted_by = user - tag.deleted_at = datetime.datetime.now() + tag.deleted_at = timestamp else: tag.used_count = tag.used_count - 1 tag.save() @@ -496,3 +517,8 @@ def onDeleted(post, user): elif isinstance(post, Answer): Question.objects.update_answer_count(post.question) logging.debug('updated answer count to %d' % post.question.answer_count) + delete_post_or_answer.send( + sender=post.__class__, + instance=post, + delete_by=user + ) diff --git a/forum/authentication/__init__.py b/forum/authentication/__init__.py new file mode 100755 index 00000000..75099303 --- /dev/null +++ b/forum/authentication/__init__.py @@ -0,0 +1,27 @@ +import re +from forum.modules import get_modules_script_classes +from forum.authentication.base import AuthenticationConsumer, ConsumerTemplateContext + +class ConsumerAndContext(): + def __init__(self, id, consumer, context): + self.id = id + self.consumer = consumer() + + context.id = id #add extra field to context + self.context = context + +consumers = dict([ + (re.sub('AuthConsumer$', '', name).lower(), cls) for name, cls + in get_modules_script_classes('authentication', AuthenticationConsumer).items() + if not re.search('AbstractAuthConsumer$', name) + ]) + +contexts = dict([ + (re.sub('AuthContext$', '', name).lower(), cls) for name, cls + in get_modules_script_classes('authentication', ConsumerTemplateContext).items() + ]) + +AUTH_PROVIDERS = dict([ + (name, ConsumerAndContext(name, consumers[name], contexts[name])) for name in consumers.keys() + if name in contexts + ]) diff --git a/forum/authentication/base.py b/forum/authentication/base.py new file mode 100755 index 00000000..99005866 --- /dev/null +++ b/forum/authentication/base.py @@ -0,0 +1,44 @@ + +class AuthenticationConsumer(object): + + def prepare_authentication_request(self, request, redirect_to): + raise NotImplementedError() + + def process_authentication_request(self, response): + raise NotImplementedError() + + def get_user_data(self, key): + raise NotImplementedError() + + +class ConsumerTemplateContext(object): + """ + Class that provides information about a certain authentication provider context in the signin page. + + class attributes: + + mode - one of BIGICON, SMALLICON, FORM + + human_name - the human readable name of the provider + + extra_js - some providers require us to load extra javascript on the signin page for them to work, + this is the place to add those files in the form of a list + + extra_css - same as extra_js but for css files + """ + mode = '' + weight = 500 + human_name = '' + extra_js = [] + extra_css = [] + show_to_logged_in_user = True + + @classmethod + def readable_key(cls, key): + return key.key + +class InvalidAuthentication(Exception): + def __init__(self, message): + self.message = message + +
\ No newline at end of file diff --git a/forum/authentication/forms.py b/forum/authentication/forms.py new file mode 100755 index 00000000..7fa06b01 --- /dev/null +++ b/forum/authentication/forms.py @@ -0,0 +1,73 @@ +from forum.utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm +from forum.models import EmailFeedSetting, Question, User +from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext as _ +from django.utils.safestring import mark_safe +from django import forms +from forum.forms import EditUserEmailFeedsForm +import logging + +class SimpleRegistrationForm(forms.Form): + next = NextUrlField() + username = UserNameField() + email = UserEmailField() + +class TemporaryLoginRequestForm(forms.Form): + def __init__(self, data=None): + super(TemporaryLoginRequestForm, self).__init__(data) + self.user_cache = None + + email = forms.EmailField( + required=True, + label=_("Your account email"), + error_messages={ + 'required': _("You cannot leave this field blank"), + 'invalid': _('please enter a valid email address'), + } + ) + + def clean_email(self): + try: + user = User.objects.get(email=self.cleaned_data['email']) + except: + raise forms.ValidationError(_("Sorry, but this email is not on our database.")) + + self.user_cache = user + return self.cleaned_data['email'] + + +class SimpleEmailSubscribeForm(forms.Form): + SIMPLE_SUBSCRIBE_CHOICES = ( + ('y',_('okay, let\'s try!')), + ('n',_('no OSQA community email please, thanks')) + ) + subscribe = forms.ChoiceField(widget=forms.widgets.RadioSelect(), \ + error_messages={'required':_('please choose one of the options above')}, + choices=SIMPLE_SUBSCRIBE_CHOICES) + + def save(self,user=None): + EFF = EditUserEmailFeedsForm + if self.cleaned_data['subscribe'] == 'y': + email_settings_form = EFF() + logging.debug('%s wants to subscribe' % user.username) + else: + email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL) + email_settings_form.save(user,save_unbound=True) + +class ChangePasswordForm(SetPasswordForm): + """ change password form """ + oldpw = forms.CharField(widget=forms.PasswordInput(attrs={'class':'required'}), + label=mark_safe(_('Current password'))) + + def __init__(self, data=None, user=None, *args, **kwargs): + if user is None: + raise TypeError("Keyword argument 'user' must be supplied") + super(ChangePasswordForm, self).__init__(data, *args, **kwargs) + self.user = user + + def clean_oldpw(self): + """ test old password """ + if not self.user.check_password(self.cleaned_data['oldpw']): + raise forms.ValidationError(_("Old password is incorrect. \ + Please enter the correct password.")) + return self.cleaned_data['oldpw'] diff --git a/forum/badges/__init__.py b/forum/badges/__init__.py new file mode 100755 index 00000000..8d7cd097 --- /dev/null +++ b/forum/badges/__init__.py @@ -0,0 +1,10 @@ +import re + +from forum.badges.base import BadgeImplementation +from forum.modules import get_modules_script_classes + +ALL_BADGES = dict([ + (re.sub('BadgeImpl', '', name).lower(), cls) for name, cls + in get_modules_script_classes('badges', BadgeImplementation).items() + if not re.search('AbstractBadgeImpl$', name) + ])
\ No newline at end of file diff --git a/forum/badges/base.py b/forum/badges/base.py new file mode 100755 index 00000000..03ef3565 --- /dev/null +++ b/forum/badges/base.py @@ -0,0 +1,11 @@ + + +class BadgeImplementation(object): + name = "" + description = "" + + def install(self): + pass + + def process_job(self): + raise NotImplementedError
\ No newline at end of file diff --git a/forum/const.py b/forum/const.py index 76fd4a24..4c107572 100644..100755 --- a/forum/const.py +++ b/forum/const.py @@ -8,12 +8,12 @@ CLOSE_REASONS = ( (1, _('duplicate question')), (2, _('question is off-topic or not relevant')), (3, _('too subjective and argumentative')), - (4, _('is not an answer to the question')), + (4, _('not a real question')), (5, _('the question is answered, right answer was accepted')), - (6, _('problem is not reproducible or outdated')), - #(7, u'太局部、本地化的问题',) - (7, _('question contains offensive inappropriate, or malicious remarks')), + (6, _('question is not relevant or outdated')), + (7, _('question contains offensive or malicious remarks')), (8, _('spam or advertising')), + (9, _('too localized')), ) TYPE_REPUTATION = ( @@ -75,10 +75,10 @@ TYPE_ACTIVITY = ( ) TYPE_RESPONSE = { - 'QUESTION_ANSWERED' : 'question_answered', - 'QUESTION_COMMENTED': 'question_commented', - 'ANSWER_COMMENTED' : 'answer_commented', - 'ANSWER_ACCEPTED' : 'answer_accepted', + 'QUESTION_ANSWERED' : _('question_answered'), + 'QUESTION_COMMENTED': _('question_commented'), + 'ANSWER_COMMENTED' : _('answer_commented'), + 'ANSWER_ACCEPTED' : _('answer_accepted'), } CONST = { @@ -90,3 +90,4 @@ CONST = { #how to filter questions by tags in email digests? TAG_EMAIL_FILTER_CHOICES = (('ignored', _('exclude ignored tags')),('interesting',_('allow only selected tags'))) +MAX_ALERTS_PER_EMAIL = 7 diff --git a/context.py b/forum/context.py index 8290c3b6..905d24dd 100644 --- a/context.py +++ b/forum/context.py @@ -8,15 +8,13 @@ def application_settings(context): 'APP_DESCRIPTION' : settings.APP_DESCRIPTION, 'APP_INTRO' : settings.APP_INTRO, 'EMAIL_VALIDATION': settings.EMAIL_VALIDATION, + 'FEEDBACK_SITE_URL': settings.FEEDBACK_SITE_URL, + 'FORUM_SCRIPT_ALIAS': settings.FORUM_SCRIPT_ALIAS, 'LANGUAGE_CODE': settings.LANGUAGE_CODE, 'GOOGLE_SITEMAP_CODE':settings.GOOGLE_SITEMAP_CODE, 'GOOGLE_ANALYTICS_KEY':settings.GOOGLE_ANALYTICS_KEY, - 'BOOKS_ON':settings.BOOKS_ON, 'WIKI_ON':settings.WIKI_ON, - 'FORUM_SCRIPT_ALIAS':settings.FORUM_SCRIPT_ALIAS, - 'USE_EXTERNAL_LEGACY_LOGIN':settings.USE_EXTERNAL_LEGACY_LOGIN, 'RESOURCE_REVISION':settings.RESOURCE_REVISION, - 'USE_SPHINX_SEARCH':settings.USE_SPHINX_SEARCH, 'OSQA_SKIN':settings.OSQA_DEFAULT_SKIN, } return {'settings':my_settings} diff --git a/forum/feed.py b/forum/feed.py index e4b929e9..e4b929e9 100644..100755 --- a/forum/feed.py +++ b/forum/feed.py diff --git a/forum/forms.py b/forum/forms.py index 2212bb34..1953bbe2 100644..100755 --- a/forum/forms.py +++ b/forum/forms.py @@ -5,12 +5,14 @@ from models import * from const import * from django.utils.translation import ugettext as _ from django.contrib.auth.models import User -from forum.utils.forms import NextUrlField, UserNameField from django.contrib.contenttypes.models import ContentType +from forum.utils.forms import NextUrlField, UserNameField, SetPasswordForm from recaptcha_django import ReCaptchaField from django.conf import settings +from django.contrib.contenttypes.models import ContentType import logging + class TitleField(forms.CharField): def __init__(self, *args, **kwargs): super(TitleField, self).__init__(*args, **kwargs) @@ -39,7 +41,6 @@ class EditorField(forms.CharField): def clean(self, value): if len(value) < 10: raise forms.ValidationError(_('question content must be > 10 characters')) - return value class TagNamesField(forms.CharField): @@ -183,6 +184,7 @@ class EditQuestionForm(forms.Form): tags = TagNamesField() summary = SummaryField() + #todo: this is odd that this form takes question as an argument def __init__(self, question, revision, *args, **kwargs): super(EditQuestionForm, self).__init__(*args, **kwargs) self.fields['title'].initial = revision.title @@ -259,6 +261,7 @@ class TagFilterSelectionForm(forms.ModelForm): if before != after: return True return False + class EditUserEmailFeedsForm(forms.Form): WN = (('w',_('weekly')),('n',_('no email'))) @@ -291,7 +294,7 @@ class EditUserEmailFeedsForm(forms.Form): def set_initial_values(self,user=None): KEY_MAP = dict([(v,k) for k,v in self.FORM_TO_MODEL_MAP.iteritems()]) if user != None: - settings = EmailFeedSetting.objects.filter(subscriber=user) + settings = EmailFeedSetting.objects.filter(subscriber=user) initial_values = {} for setting in settings: feed_type = setting.feed_type @@ -302,10 +305,11 @@ class EditUserEmailFeedsForm(forms.Form): return self def reset(self): - self.cleaned_data['all_questions'] = 'n' - self.cleaned_data['asked_by_me'] = 'n' - self.cleaned_data['answered_by_me'] = 'n' - self.cleaned_data['individually_selected'] = 'n' + if self.is_bound: + self.cleaned_data['all_questions'] = 'n' + self.cleaned_data['asked_by_me'] = 'n' + self.cleaned_data['answered_by_me'] = 'n' + self.cleaned_data['individually_selected'] = 'n' self.initial = self.NO_EMAIL_INITIAL return self @@ -337,7 +341,6 @@ class EditUserEmailFeedsForm(forms.Form): user.followed_questions.clear() return changed - class SimpleEmailSubscribeForm(forms.Form): SIMPLE_SUBSCRIBE_CHOICES = ( ('y',_('okay, let\'s try!')), diff --git a/forum/management/__init__.py b/forum/management/__init__.py index b654caaa..b654caaa 100644..100755 --- a/forum/management/__init__.py +++ b/forum/management/__init__.py diff --git a/forum/management/commands/__init__.py b/forum/management/commands/__init__.py index e69de29b..e69de29b 100644..100755 --- a/forum/management/commands/__init__.py +++ b/forum/management/commands/__init__.py diff --git a/forum/management/commands/base_command.py b/forum/management/commands/base_command.py index c073bf7a..c073bf7a 100644..100755 --- a/forum/management/commands/base_command.py +++ b/forum/management/commands/base_command.py diff --git a/forum/management/commands/clean_award_badges.py b/forum/management/commands/clean_award_badges.py index 117e3a5f..117e3a5f 100644..100755 --- a/forum/management/commands/clean_award_badges.py +++ b/forum/management/commands/clean_award_badges.py diff --git a/forum/management/commands/message_to_everyone.py b/forum/management/commands/message_to_everyone.py index c020c178..c020c178 100644..100755 --- a/forum/management/commands/message_to_everyone.py +++ b/forum/management/commands/message_to_everyone.py diff --git a/forum/management/commands/multi_award_badges.py b/forum/management/commands/multi_award_badges.py index 6b330cf9..6b330cf9 100644..100755 --- a/forum/management/commands/multi_award_badges.py +++ b/forum/management/commands/multi_award_badges.py diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py index 8c913348..372eb3aa 100644..100755 --- a/forum/management/commands/once_award_badges.py +++ b/forum/management/commands/once_award_badges.py @@ -337,7 +337,7 @@ class Command(BaseCommand): if user_id not in awarded_users: user = get_object_or_404(User, id=user_id) - award = Award(user=user, badge=badge) + award = Award(user=user, badge=badge)#todo: will this work with content_object null? award.save() awarded_users.append(user_id) finally: diff --git a/forum/management/commands/pg_base_command.py b/forum/management/commands/pg_base_command.py new file mode 100755 index 00000000..b3167dcf --- /dev/null +++ b/forum/management/commands/pg_base_command.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +#encoding:utf-8 +#------------------------------------------------------------------------------- +# Name: Award badges command +# Purpose: This is a command file croning in background process regularly to +# query database and award badges for user's special acitivities. +# +# Author: Mike, Sailing +# +# Created: 22/01/2009 +# Copyright: (c) Mike 2009 +# Licence: GPL V2 +#------------------------------------------------------------------------------- + +from datetime import datetime, date +from django.core.management.base import NoArgsCommand +from django.db import connection +from django.shortcuts import get_object_or_404 +from django.contrib.contenttypes.models import ContentType + +from forum.models import * +from forum.const import * + +class BaseCommand(NoArgsCommand): + def update_activities_auditted(self, cursor, activity_ids): + # update processed rows to auditted + if len(activity_ids): + query = "UPDATE activity SET is_auditted = TRUE WHERE id in (%s)"\ + % ','.join('%s' % item for item in activity_ids) + cursor.execute(query) + + + + + diff --git a/forum/management/commands/pg_clean_award_badges.py b/forum/management/commands/pg_clean_award_badges.py new file mode 100755 index 00000000..b3925a68 --- /dev/null +++ b/forum/management/commands/pg_clean_award_badges.py @@ -0,0 +1,59 @@ +#------------------------------------------------------------------------------- +# Name: Award badges command +# Purpose: This is a command file croning in background process regularly to +# query database and award badges for user's special acitivities. +# +# Author: Mike +# +# Created: 18/01/2009 +# Copyright: (c) Mike 2009 +# Licence: GPL V2 +#------------------------------------------------------------------------------- +#!/usr/bin/env python +#encoding:utf-8 +from django.core.management.base import NoArgsCommand +from django.db import connection +from django.shortcuts import get_object_or_404 +from django.contrib.contenttypes.models import ContentType + +from forum.models import * + +class Command(NoArgsCommand): + def handle_noargs(self, **options): + try: + try: + self.clean_awards() + except Exception, e: + print e + finally: + connection.close() + + def clean_awards(self): + Award.objects.all().delete() + + award_type =ContentType.objects.get_for_model(Award) + Activity.objects.filter(content_type=award_type).delete() + + for user in User.objects.all(): + user.gold = 0 + user.silver = 0 + user.bronze = 0 + user.save() + + for badge in Badge.objects.all(): + badge.awarded_count = 0 + badge.save() + + query = "UPDATE activity SET is_auditted = FALSE" + cursor = connection.cursor() + try: + cursor.execute(query) + finally: + cursor.close() + connection.close() + +def main(): + pass + +if __name__ == '__main__': + main()
\ No newline at end of file diff --git a/forum/management/commands/pg_multi_award_badges.py b/forum/management/commands/pg_multi_award_badges.py new file mode 100755 index 00000000..75f84bfe --- /dev/null +++ b/forum/management/commands/pg_multi_award_badges.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python +#encoding:utf-8 +#------------------------------------------------------------------------------- +# Name: Award badges command +# Purpose: This is a command file croning in background process regularly to +# query database and award badges for user's special acitivities. +# +# Author: Mike, Sailing +# +# Created: 22/01/2009 +# Copyright: (c) Mike 2009 +# Licence: GPL V2 +#------------------------------------------------------------------------------- + +from datetime import datetime, date +from django.core.management.base import NoArgsCommand +from django.db import connection +from django.shortcuts import get_object_or_404 +from django.contrib.contenttypes.models import ContentType + +from forum.models import * +from forum.const import * +from pg_base_command import BaseCommand +""" +(1, '????', 3, '????', '?????3?????????', 1, 0), +(2, '????', 3, '????', '?????3?????????', 1, 0), +(3, '????', 3, '????', '????10???', 1, 0), +(4, '????', 3, '????', '????10???', 1, 0), +(5, '???', 3, '???', '??10???', 0, 0), +(6, '????', 3, '????', '????????1000??', 1, 0), +(7, '???', 3, '???', '?????????', 0, 0), +(8, '???', 3, '???', '???????', 0, 0), +(9, '???', 3, '???', '??????', 0, 0), +(10, '??', 3, '??', '???????', 0, 0), +(11, '??', 3, '??', '???????', 0, 0), +(12, '??', 3, '??', '???????', 0, 0), +(13, '??', 3, '??', '???????????????', 0, 0), +(14, '???', 3, '???', '??????', 0, 0), +(15, '??', 3, '??', '??????????????????', 0, 0), +(16, '????', 3, '????', '????????????', 0, 0), +(17, '????', 3, '????', '??????????3??????', 1, 0), +(18, '??????', 1, '??????', '????100????', 1, 0), +(19, '??????', 1, '??????', '????100????', 1, 0), +(20, '???', 1, '???', '???100?????', 1, 0), +(21, '????', 1, '????', '????????10000??', 1, 0), +(22, 'alpha??', 2, 'alpha??', '?????????', 0, 0), +(23, '????', 2, '????', '????25????', 1, 0), +(24, '????', 2, '????', '????25????', 1, 0), +(25, '?????', 2, '?????', '???25?????', 1, 0), +(26, '????', 2, '????', '??300???', 0, 0), +(27, '????', 2, '????', '???100???', 0, 0), +(28, '??', 2, '??', '?????????', 0, 0), +(29, '??', 2, '??', '???????????', 0, 0), +(30, '??', 2, '??', '?????????', 0, 0), +(31, '??????', 2, '??????', '????????2500??', 1, 0), +(32, '???', 2, '???', '??????????10???', 0, 0), +(33, 'beta??', 2, 'beta??', 'beta??????', 0, 0), +(34, '??', 2, '??', '?????????????40??', 1, 0), +(35, '??', 2, '??', '???60??????????5???', 1, 0), +(36, '????', 2, '????', '??????50???????', 1, 0); + + +TYPE_ACTIVITY_ASK_QUESTION=1 +TYPE_ACTIVITY_ANSWER=2 +TYPE_ACTIVITY_COMMENT_QUESTION=3 +TYPE_ACTIVITY_COMMENT_ANSWER=4 +TYPE_ACTIVITY_UPDATE_QUESTION=5 +TYPE_ACTIVITY_UPDATE_ANSWER=6 +TYPE_ACTIVITY_PRIZE=7 +TYPE_ACTIVITY_MARK_ANSWER=8 +TYPE_ACTIVITY_VOTE_UP=9 +TYPE_ACTIVITY_VOTE_DOWN=10 +TYPE_ACTIVITY_CANCEL_VOTE=11 +TYPE_ACTIVITY_DELETE_QUESTION=12 +TYPE_ACTIVITY_DELETE_ANSWER=13 +TYPE_ACTIVITY_MARK_OFFENSIVE=14 +TYPE_ACTIVITY_UPDATE_TAGS=15 +TYPE_ACTIVITY_FAVORITE=16 +TYPE_ACTIVITY_USER_FULL_UPDATED = 17 +""" + +class Command(BaseCommand): + def handle_noargs(self, **options): + try: + try: + self.delete_question_be_voted_up_3() + self.delete_answer_be_voted_up_3() + self.delete_question_be_vote_down_3() + self.delete_answer_be_voted_down_3() + self.answer_be_voted_up_10() + self.question_be_voted_up_10() + self.question_view_1000() + self.answer_self_question_be_voted_up_3() + self.answer_be_voted_up_100() + self.question_be_voted_up_100() + self.question_be_favorited_100() + self.question_view_10000() + self.answer_be_voted_up_25() + self.question_be_voted_up_25() + self.question_be_favorited_25() + self.question_view_2500() + self.answer_be_accepted_and_voted_up_40() + self.question_be_answered_after_60_days_and_be_voted_up_5() + self.created_tag_be_used_in_question_50() + except Exception, e: + print e + finally: + connection.close() + + def delete_question_be_voted_up_3(self): + """ + (1, '????', 3, '????', '?????3?????????', 1, 0), + """ + query = "SELECT act.id, act.user_id, act.object_id FROM activity act, question q WHERE act.object_id = q.id AND\ + act.activity_type = %s AND\ + q.vote_up_count >=3 AND \ + not act.is_auditted" % (TYPE_ACTIVITY_DELETE_QUESTION) + self.__process_activities_badge(query, 1, Question) + + def delete_answer_be_voted_up_3(self): + """ + (1, '????', 3, '????', '?????3?????????', 1, 0), + """ + query = "SELECT act.id, act.user_id, act.object_id FROM activity act, answer an WHERE act.object_id = an.id AND\ + act.activity_type = %s AND\ + an.vote_up_count >=3 AND \ + not act.is_auditted" % (TYPE_ACTIVITY_DELETE_ANSWER) + self.__process_activities_badge(query, 1, Answer) + + def delete_question_be_vote_down_3(self): + """ + (2, '????', 3, '????', '?????3?????????', 1, 0), + """ + query = "SELECT act.id, act.user_id, act.object_id FROM activity act, question q WHERE act.object_id = q.id AND\ + act.activity_type = %s AND\ + q.vote_down_count >=3 AND \ + not act.is_auditted" % (TYPE_ACTIVITY_DELETE_QUESTION) + content_type = ContentType.objects.get_for_model(Question) + self.__process_activities_badge(query, 2, Question) + + def delete_answer_be_voted_down_3(self): + """ + (2, '????', 3, '????', '?????3?????????', 1, 0), + """ + query = "SELECT act.id, act.user_id, act.object_id FROM activity act, answer an WHERE act.object_id = an.id AND\ + act.activity_type = %s AND\ + an.vote_down_count >=3 AND \ + not act.is_auditted" % (TYPE_ACTIVITY_DELETE_ANSWER) + self.__process_activities_badge(query, 2, Answer) + + def answer_be_voted_up_10(self): + """ + (3, '????', 3, '????', '????10???', 1, 0), + """ + query = "SELECT act.id, act.user_id, act.object_id FROM \ + activity act, answer a WHERE act.object_id = a.id AND\ + act.activity_type = %s AND \ + a.vote_up_count >= 10 AND\ + not act.is_auditted" % (TYPE_ACTIVITY_ANSWER) + self.__process_activities_badge(query, 3, Answer) + + def question_be_voted_up_10(self): + """ + (4, '????', 3, '????', '????10???', 1, 0), + """ + query = "SELECT act.id, act.user_id, act.object_id FROM \ + activity act, question q WHERE act.object_id = q.id AND\ + act.activity_type = %s AND \ + q.vote_up_count >= 10 AND\ + not act.is_auditted" % (TYPE_ACTIVITY_ASK_QUESTION) + self.__process_activities_badge(query, 4, Question) + + def question_view_1000(self): + """ + (6, '????', 3, '????', '????????1000??', 1, 0), + """ + query = "SELECT act.id, act.user_id, act.object_id FROM \ + activity act, question q WHERE act.activity_type = %s AND\ + act.object_id = q.id AND \ + q.view_count >= 1000 AND\ + act.object_id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 6) + self.__process_activities_badge(query, 6, Question, False) + + def answer_self_question_be_voted_up_3(self): + """ + (17, '????', 3, '????', '??????????3??????', 1, 0), + """ + query = "SELECT act.id, act.user_id, act.object_id FROM \ + activity act, answer an WHERE act.activity_type = %s AND\ + act.object_id = an.id AND\ + an.vote_up_count >= 3 AND\ + act.user_id = (SELECT user_id FROM question q WHERE q.id = an.question_id) AND\ + act.object_id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 17) + self.__process_activities_badge(query, 17, Question, False) + + def answer_be_voted_up_100(self): + """ + (18, '??????', 1, '??????', '????100????', 1, 0), + """ + query = "SELECT an.id, an.author_id FROM answer an WHERE an.vote_up_count >= 100 AND an.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (18) + + self.__process_badge(query, 18, Answer) + + def question_be_voted_up_100(self): + """ + (19, '??????', 1, '??????', '????100????', 1, 0), + """ + query = "SELECT q.id, q.author_id FROM question q WHERE q.vote_up_count >= 100 AND q.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (19) + + self.__process_badge(query, 19, Question) + + def question_be_favorited_100(self): + """ + (20, '???', 1, '???', '???100?????', 1, 0), + """ + query = "SELECT q.id, q.author_id FROM question q WHERE q.favourite_count >= 100 AND q.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (20) + + self.__process_badge(query, 20, Question) + + def question_view_10000(self): + """ + (21, '????', 1, '????', '????????10000??', 1, 0), + """ + query = "SELECT q.id, q.author_id FROM question q WHERE q.view_count >= 10000 AND q.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (21) + + self.__process_badge(query, 21, Question) + + def answer_be_voted_up_25(self): + """ + (23, '????', 2, '????', '????25????', 1, 0), + """ + query = "SELECT a.id, a.author_id FROM answer a WHERE a.vote_up_count >= 25 AND a.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (23) + + self.__process_badge(query, 23, Answer) + + def question_be_voted_up_25(self): + """ + (24, '????', 2, '????', '????25????', 1, 0), + """ + query = "SELECT q.id, q.author_id FROM question q WHERE q.vote_up_count >= 25 AND q.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (24) + + self.__process_badge(query, 24, Question) + + def question_be_favorited_25(self): + """ + (25, '?????', 2, '?????', '???25?????', 1, 0), + """ + query = "SELECT q.id, q.author_id FROM question q WHERE q.favourite_count >= 25 AND q.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (25) + + self.__process_badge(query, 25, Question) + + def question_view_2500(self): + """ + (31, '??????', 2, '??????', '????????2500??', 1, 0), + """ + query = "SELECT q.id, q.author_id FROM question q WHERE q.view_count >= 2500 AND q.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (31) + + self.__process_badge(query, 31, Question) + + def answer_be_accepted_and_voted_up_40(self): + """ + (34, '??', 2, '??', '?????????????40??', 1, 0), + """ + query = "SELECT a.id, a.author_id FROM answer a WHERE a.vote_up_count >= 40 AND\ + a.accepted AND\ + a.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (34) + + self.__process_badge(query, 34, Answer) + + def question_be_answered_after_60_days_and_be_voted_up_5(self): + """ + (35, '??', 2, '??', '???60??????????5???', 1, 0), + """ + query = "SELECT a.id, a.author_id FROM question q, answer a WHERE q.id = a.question_id AND\ + (a.added_at + '60 day'::INTERVAL) >= q.added_at AND\ + a.vote_up_count >= 5 AND \ + a.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (35) + + self.__process_badge(query, 35, Answer) + + def created_tag_be_used_in_question_50(self): + """ + (36, '????', 2, '????', '??????50???????', 1, 0); + """ + query = "SELECT t.id, t.created_by_id FROM tag t, auth_user u WHERE t.created_by_id = u.id AND \ + t. used_count >= 50 AND \ + t.id NOT IN \ + (SELECT object_id FROM award WHERE award.badge_id = %s)" % (36) + + self.__process_badge(query, 36, Tag) + + def __process_activities_badge(self, query, badge, content_object, update_auditted=True): + content_type = ContentType.objects.get_for_model(content_object) + + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + if update_auditted: + activity_ids = [] + badge = get_object_or_404(Badge, id=badge) + for row in rows: + activity_id = row[0] + user_id = row[1] + object_id = row[2] + + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + + if update_auditted: + activity_ids.append(activity_id) + + if update_auditted: + self.update_activities_auditted(cursor, activity_ids) + finally: + cursor.close() + + def __process_badge(self, query, badge, content_object): + content_type = ContentType.objects.get_for_model(Answer) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + badge = get_object_or_404(Badge, id=badge) + for row in rows: + object_id = row[0] + user_id = row[1] + + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + finally: + cursor.close() diff --git a/forum/management/commands/pg_once_award_badges.py b/forum/management/commands/pg_once_award_badges.py new file mode 100755 index 00000000..b2f79363 --- /dev/null +++ b/forum/management/commands/pg_once_award_badges.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python +#encoding:utf-8 +#------------------------------------------------------------------------------- +# Name: Award badges command +# Purpose: This is a command file croning in background process regularly to +# query database and award badges for user's special acitivities. +# +# Author: Mike, Sailing +# +# Created: 18/01/2009 +# Copyright: (c) Mike 2009 +# Licence: GPL V2 +#------------------------------------------------------------------------------- + +from datetime import datetime, date +from django.db import connection +from django.shortcuts import get_object_or_404 +from django.contrib.contenttypes.models import ContentType + +from forum.models import * +from forum.const import * +from pg_base_command import BaseCommand +""" +(1, '????', 3, '????', '?????3?????????', 1, 0), +(2, '????', 3, '????', '?????3?????????', 1, 0), +(3, '????', 3, '????', '????10???', 1, 0), +(4, '????', 3, '????', '????10???', 1, 0), +(5, '???', 3, '???', '??10???', 0, 0), +(6, '????', 3, '????', '????????1000??', 1, 0), +(7, '???', 3, '???', '?????????', 0, 0), +(8, '???', 3, '???', '???????', 0, 0), +(9, '???', 3, '???', '??????', 0, 0), +(10, '??', 3, '??', '???????', 0, 0), +(11, '??', 3, '??', '???????', 0, 0), +(12, '??', 3, '??', '???????', 0, 0), +(13, '??', 3, '??', '???????????????', 0, 0), +(14, '???', 3, '???', '??????', 0, 0), +(15, '??', 3, '??', '??????????????????', 0, 0), +(16, '????', 3, '????', '????????????', 0, 0), +(17, '????', 3, '????', '??????????3??????', 1, 0), +(18, '??????', 1, '??????', '????100????', 1, 0), +(19, '??????', 1, '??????', '????100????', 1, 0), +(20, '???', 1, '???', '???100?????', 1, 0), +(21, '????', 1, '????', '????????10000??', 1, 0), +(22, 'alpha??', 2, 'alpha??', '?????????', 0, 0), +(23, '????', 2, '????', '????25????', 1, 0), +(24, '????', 2, '????', '????25????', 1, 0), +(25, '?????', 2, '?????', '???25?????', 1, 0), +(26, '????', 2, '????', '??300???', 0, 0), +(27, '????', 2, '????', '???100???', 0, 0), +(28, '??', 2, '??', '?????????', 0, 0), +(29, '??', 2, '??', '???????????', 0, 0), +(30, '??', 2, '??', '?????????', 0, 0), +(31, '??????', 2, '??????', '????????2500??', 1, 0), +(32, '???', 2, '???', '??????????10???', 0, 0), +(33, 'beta??', 2, 'beta??', 'beta??????', 0, 0), +(34, '??', 2, '??', '?????????????40??', 1, 0), +(35, '??', 2, '??', '???60??????????5???', 1, 0), +(36, '????', 2, '????', '??????50???????', 1, 0); + + +TYPE_ACTIVITY_ASK_QUESTION=1 +TYPE_ACTIVITY_ANSWER=2 +TYPE_ACTIVITY_COMMENT_QUESTION=3 +TYPE_ACTIVITY_COMMENT_ANSWER=4 +TYPE_ACTIVITY_UPDATE_QUESTION=5 +TYPE_ACTIVITY_UPDATE_ANSWER=6 +TYPE_ACTIVITY_PRIZE=7 +TYPE_ACTIVITY_MARK_ANSWER=8 +TYPE_ACTIVITY_VOTE_UP=9 +TYPE_ACTIVITY_VOTE_DOWN=10 +TYPE_ACTIVITY_CANCEL_VOTE=11 +TYPE_ACTIVITY_DELETE_QUESTION=12 +TYPE_ACTIVITY_DELETE_ANSWER=13 +TYPE_ACTIVITY_MARK_OFFENSIVE=14 +TYPE_ACTIVITY_UPDATE_TAGS=15 +TYPE_ACTIVITY_FAVORITE=16 +TYPE_ACTIVITY_USER_FULL_UPDATED = 17 +""" + +BADGE_AWARD_TYPE_FIRST = { + TYPE_ACTIVITY_MARK_OFFENSIVE : 7, + TYPE_ACTIVITY_CANCEL_VOTE: 8, + TYPE_ACTIVITY_VOTE_DOWN : 9, + TYPE_ACTIVITY_UPDATE_QUESTION : 10, + TYPE_ACTIVITY_UPDATE_ANSWER : 10, + TYPE_ACTIVITY_UPDATE_TAGS : 11, + TYPE_ACTIVITY_MARK_ANSWER : 12, + TYPE_ACTIVITY_VOTE_UP : 14, + TYPE_ACTIVITY_USER_FULL_UPDATED: 16 + +} + +class Command(BaseCommand): + def handle_noargs(self, **options): + try: + try: + self.alpha_user() + self.beta_user() + self.first_type_award() + self.first_ask_be_voted() + self.first_answer_be_voted() + self.first_answer_be_voted_10() + self.vote_count_300() + self.edit_count_100() + self.comment_count_10() + except Exception, e: + print e + finally: + connection.close() + + def alpha_user(self): + """ + Before Jan 25, 2009(Chinese New Year Eve and enter into Beta for CNProg), every registered user + will be awarded the "Alpha" badge if he has any activities. + """ + alpha_end_date = date(2009, 1, 25) + if date.today() < alpha_end_date: + badge = get_object_or_404(Badge, id=22) + for user in User.objects.all(): + award = Award.objects.filter(user=user, badge=badge) + if award and not badge.multiple: + continue + activities = Activity.objects.filter(user=user) + if len(activities) > 0: + new_award = Award(user=user, badge=badge) + new_award.save() + + def beta_user(self): + """ + Before Feb 25, 2009, every registered user + will be awarded the "Beta" badge if he has any activities. + """ + beta_end_date = date(2009, 2, 25) + if date.today() < beta_end_date: + badge = get_object_or_404(Badge, id=33) + for user in User.objects.all(): + award = Award.objects.filter(user=user, badge=badge) + if award and not badge.multiple: + continue + activities = Activity.objects.filter(user=user) + if len(activities) > 0: + new_award = Award(user=user, badge=badge) + new_award.save() + + def first_type_award(self): + """ + This will award below badges for users first behaviors: + + (7, '???', 3, '???', '?????????', 0, 0), + (8, '???', 3, '???', '???????', 0, 0), + (9, '???', 3, '???', '??????', 0, 0), + (10, '??', 3, '??', '???????', 0, 0), + (11, '??', 3, '??', '???????', 0, 0), + (12, '??', 3, '??', '???????', 0, 0), + (14, '???', 3, '???', '??????', 0, 0), + (16, '????', 3, '????', '????????????', 0, 0), + """ + activity_types = ','.join('%s' % item for item in BADGE_AWARD_TYPE_FIRST.keys()) + # ORDER BY user_id, activity_type + query = "SELECT id, user_id, activity_type, content_type_id, object_id FROM activity WHERE not is_auditted AND activity_type IN (%s) ORDER BY user_id, activity_type" % activity_types + + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + # collect activity_id in current process + activity_ids = [] + last_user_id = 0 + last_activity_type = 0 + for row in rows: + activity_ids.append(row[0]) + user_id = row[1] + activity_type = row[2] + content_type_id = row[3] + object_id = row[4] + + # if the user and activity are same as the last, continue + if user_id == last_user_id and activity_type == last_activity_type: + continue; + + user = get_object_or_404(User, id=user_id) + badge = get_object_or_404(Badge, id=BADGE_AWARD_TYPE_FIRST[activity_type]) + content_type = get_object_or_404(ContentType, id=content_type_id) + + count = Award.objects.filter(user=user, badge=badge).count() + if count and not badge.multiple: + continue + else: + # new award + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + + # set the current user_id and activity_type to last + last_user_id = user_id + last_activity_type = activity_type + + # update processed rows to auditted + self.update_activities_auditted(cursor, activity_ids) + finally: + cursor.close() + + def first_ask_be_voted(self): + """ + For user asked question and got first upvote, we award him following badge: + + (13, '??', 3, '??', '???????????????', 0, 0), + """ + query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM " \ + "activity act, question q WHERE act.activity_type = %s AND " \ + "act.object_id = q.id AND " \ + "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 13) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + badge = get_object_or_404(Badge, id=13) + content_type = ContentType.objects.get_for_model(Question) + awarded_users = [] + for row in rows: + user_id = row[0] + vote_up_count = row[1] + object_id = row[2] + if vote_up_count > 0 and user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + + def first_answer_be_voted(self): + """ + When user answerd questions and got first upvote, we award him following badge: + + (15, '??', 3, '??', '??????????????????', 0, 0), + """ + query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM " \ + "activity act, answer a WHERE act.activity_type = %s AND " \ + "act.object_id = a.id AND " \ + "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 15) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + awarded_users = [] + badge = get_object_or_404(Badge, id=15) + content_type = ContentType.objects.get_for_model(Answer) + for row in rows: + user_id = row[0] + vote_up_count = row[1] + object_id = row[2] + if vote_up_count > 0 and user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + + def first_answer_be_voted_10(self): + """ + (32, '???', 2, '???', '??????????10???', 0, 0) + """ + query = "SELECT act.user_id, act.object_id FROM " \ + "activity act, answer a WHERE act.object_id = a.id AND " \ + "act.activity_type = %s AND " \ + "a.vote_up_count >= 10 AND " \ + "act.user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 32) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + awarded_users = [] + badge = get_object_or_404(Badge, id=32) + content_type = ContentType.objects.get_for_model(Answer) + for row in rows: + user_id = row[0] + if user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + object_id = row[1] + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + + def vote_count_300(self): + """ + (26, '????', 2, '????', '??300???', 0, 0) + """ + query = "SELECT count(*) as vote_count, user_id FROM activity WHERE " \ + "activity_type = %s OR " \ + "activity_type = %s AND " \ + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + "GROUP BY user_id HAVING count(*) >= 300" % (TYPE_ACTIVITY_VOTE_UP, TYPE_ACTIVITY_VOTE_DOWN, 26) + + self.__award_for_count_num(query, 26) + + def edit_count_100(self): + """ + (27, '????', 2, '????', '???100???', 0, 0) + """ + query = "SELECT count(*) as vote_count, user_id FROM activity WHERE " \ + "activity_type = %s OR " \ + "activity_type = %s AND " \ + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + "GROUP BY user_id HAVING count(*) >= 100" % (TYPE_ACTIVITY_UPDATE_QUESTION, TYPE_ACTIVITY_UPDATE_ANSWER, 27) + + self.__award_for_count_num(query, 27) + + def comment_count_10(self): + """ + (5, '???', 3, '???', '??10???', 0, 0), + """ + query = "SELECT count(*) as vote_count, user_id FROM activity WHERE " \ + "activity_type = %s OR " \ + "activity_type = %s AND " \ + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + "GROUP BY user_id HAVING count(*) >= 10" % (TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, 5) + self.__award_for_count_num(query, 5) + + def __award_for_count_num(self, query, badge): + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + awarded_users = [] + badge = get_object_or_404(Badge, id=badge) + for row in rows: + vote_count = row[0] + user_id = row[1] + + if user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + +def main(): + pass + +if __name__ == '__main__': + main() diff --git a/forum/management/commands/sample_command.py b/forum/management/commands/sample_command.py index 55e67235..55e67235 100644..100755 --- a/forum/management/commands/sample_command.py +++ b/forum/management/commands/sample_command.py diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py index 26eb7790..42a6b3b3 100644..100755 --- a/forum/management/commands/send_email_alerts.py +++ b/forum/management/commands/send_email_alerts.py @@ -10,6 +10,28 @@ import datetime from django.conf import settings import logging from forum.utils.odict import OrderedDict +from django.contrib.contenttypes.models import ContentType +from forum import const + +def extend_question_list(src, dst, limit=False): + """src is a query set with questions + or None + dst - is an ordered dictionary + """ + if limit and len(dst.keys()) >= const.MAX_ALERTS_PER_EMAIL: + return + if src is None:#is not QuerySet + return #will not do anything if subscription of this type is not used + cutoff_time = src.cutoff_time + for q in src: + if q in dst: + #the latest cutoff time wins for a given question + #if the question falls into several subscription groups + if cutoff_time > dst[q]['cutoff_time']: + dst[q]['cutoff_time'] = cutoff_time + else: + #initialise a questions metadata dictionary to use for email reporting + dst[q] = {'cutoff_time':cutoff_time} class Command(NoArgsCommand): def handle_noargs(self,**options): @@ -22,83 +44,124 @@ class Command(NoArgsCommand): connection.close() def get_updated_questions_for_user(self,user): - q_sel = None - q_ask = None - q_ans = None - q_all = None + + #these are placeholders for separate query sets per question group + #there are four groups - one for each EmailFeedSetting.feed_type + #and each group has subtypes A and B + #that's because of the strange thing commented below + #see note on Q and F objects marked with todo tag + q_sel_A = None + q_sel_B = None + + q_ask_A = None + q_ask_B = None + + q_ans_A = None + q_ans_B = None + + q_all_A = None + q_all_B = None + now = datetime.datetime.now() + #Q_set1 - base questionquery set for this user Q_set1 = Question.objects.exclude( - last_activity_by=user, - ).exclude( - last_activity_at__lt=user.date_joined - ).filter( - Q(viewed__who=user,viewed__when__lt=F('last_activity_at')) | \ - ~Q(viewed__who=user) - ).exclude( - deleted=True - ).exclude( - closed=True - ) - + last_activity_by=user + ).exclude( + last_activity_at__lt=user.date_joined#exclude old stuff + ).exclude( + deleted=True + ).exclude( + closed=True + ).order_by('-last_activity_at') + #todo: for some reason filter on did not work as expected ~Q(viewed__who=user) | + # Q(viewed__who=user,viewed__when__lt=F('last_activity_at')) + #returns way more questions than you might think it should + #so because of that I've created separate query sets Q_set2 and Q_set3 + #plus two separate queries run faster! + + #questions that are not seen by the user + Q_set2 = Q_set1.filter(~Q(viewed__who=user)) + #questions seen before the last modification + Q_set3 = Q_set1.filter(Q(viewed__who=user,viewed__when__lt=F('last_activity_at'))) + + #todo may shortcirquit here is len(user_feeds) == 0 user_feeds = EmailFeedSetting.objects.filter(subscriber=user).exclude(frequency='n') + if len(user_feeds) == 0: + return {};#short cirquit for feed in user_feeds: + #each group of updates has it's own cutoff time + #to be saved as a new parameter for each query set + #won't send email for a given question if it has been done + #after the cutoff_time cutoff_time = now - EmailFeedSetting.DELTA_TABLE[feed.frequency] if feed.reported_at == None or feed.reported_at <= cutoff_time: - Q_set = Q_set1.exclude(last_activity_at__gt=cutoff_time)#report these excluded later + Q_set_A = Q_set2#.exclude(last_activity_at__gt=cutoff_time)#report these excluded later + Q_set_B = Q_set3#.exclude(last_activity_at__gt=cutoff_time) feed.reported_at = now feed.save()#may not actually report anything, depending on filters below if feed.feed_type == 'q_sel': - q_sel = Q_set.filter(followed_by=user) - q_sel.cutoff_time = cutoff_time #store cutoff time per query set + q_sel_A = Q_set_A.filter(followed_by=user) + q_sel_A.cutoff_time = cutoff_time #store cutoff time per query set + q_sel_B = Q_set_B.filter(followed_by=user) + q_sel_B.cutoff_time = cutoff_time #store cutoff time per query set elif feed.feed_type == 'q_ask': - q_ask = Q_set.filter(author=user) - q_ask.cutoff_time = cutoff_time + q_ask_A = Q_set_A.filter(author=user) + q_ask_A.cutoff_time = cutoff_time + q_ask_B = Q_set_B.filter(author=user) + q_ask_B.cutoff_time = cutoff_time elif feed.feed_type == 'q_ans': - q_ans = Q_set.filter(answers__author=user) - q_ans.cutoff_time = cutoff_time + q_ans_A = Q_set_A.filter(answers__author=user)[:const.MAX_ALERTS_PER_EMAIL] + q_ans_A.cutoff_time = cutoff_time + q_ans_B = Q_set_B.filter(answers__author=user)[:const.MAX_ALERTS_PER_EMAIL] + q_ans_B.cutoff_time = cutoff_time elif feed.feed_type == 'q_all': if user.tag_filter_setting == 'ignored': - ignored_tags = Tag.objects.filter(user_selections__reason='bad',user_selections__user=user) - q_all = Q_set.exclude( tags__in=ignored_tags ) + ignored_tags = Tag.objects.filter(user_selections__reason='bad', \ + user_selections__user=user) + q_all_A = Q_set_A.exclude( tags__in=ignored_tags )[:const.MAX_ALERTS_PER_EMAIL] + q_all_B = Q_set_B.exclude( tags__in=ignored_tags )[:const.MAX_ALERTS_PER_EMAIL] else: - selected_tags = Tag.objects.filter(user_selections__reason='good',user_selections__user=user) - q_all = Q_set.filter( tags__in=selected_tags ) - q_all.cutoff_time = cutoff_time + selected_tags = Tag.objects.filter(user_selections__reason='good', \ + user_selections__user=user) + q_all_A = Q_set_A.filter( tags__in=selected_tags ) + q_all_B = Q_set_B.filter( tags__in=selected_tags ) + q_all_A.cutoff_time = cutoff_time + q_all_B.cutoff_time = cutoff_time #build list in this order q_list = OrderedDict() - def extend_question_list(src, dst): - """src is a query set with questions - or an empty list - dst - is an ordered dictionary - """ - if src is None: - return #will not do anything if subscription of this type is not used - cutoff_time = src.cutoff_time - for q in src: - if q in dst: - if cutoff_time < dst[q]['cutoff_time']: - dst[q]['cutoff_time'] = cutoff_time - else: - #initialise a questions metadata dictionary to use for email reporting - dst[q] = {'cutoff_time':cutoff_time} - extend_question_list(q_sel, q_list) - extend_question_list(q_ask, q_list) - extend_question_list(q_ans, q_list) - extend_question_list(q_all, q_list) + extend_question_list(q_sel_A, q_list) + extend_question_list(q_sel_B, q_list) + + if user.tag_filter_setting == 'interesting': + extend_question_list(q_all_A, q_list) + extend_question_list(q_all_B, q_list) + + extend_question_list(q_ask_A, q_list, limit=True) + extend_question_list(q_ask_B, q_list, limit=True) + + extend_question_list(q_ans_A, q_list, limit=True) + extend_question_list(q_ans_B, q_list, limit=True) + + if user.tag_filter_setting == 'ignored': + extend_question_list(q_all_A, q_list, limit=True) + extend_question_list(q_all_B, q_list, limit=True) ctype = ContentType.objects.get_for_model(Question) EMAIL_UPDATE_ACTIVITY = const.TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT for q, meta_data in q_list.items(): - #todo use Activity, but first start keeping more Activity records - #act = Activity.objects.filter(content_type=ctype, object_id=q.id) - #because currently activity is not fully recorded to through - #revision records to see what kind modifications were done on - #the questions and answers + #this loop edits meta_data for each question + #so that user will receive counts on new edits new answers, etc + #maybe not so important actually?? + + #keeps email activity per question per user try: - update_info = Activity.objects.get(content_type=ctype, + update_info = Activity.objects.get( + user=user, + content_type=ctype, object_id=q.id, - activity_type=EMAIL_UPDATE_ACTIVITY) + activity_type=EMAIL_UPDATE_ACTIVITY + ) emailed_at = update_info.active_at except Activity.DoesNotExist: update_info = Activity(user=user, content_object=q, activity_type=EMAIL_UPDATE_ACTIVITY) @@ -106,10 +169,22 @@ class Command(NoArgsCommand): except Activity.MultipleObjectsReturned: raise Exception('server error - multiple question email activities found per user-question pair') + cutoff_time = meta_data['cutoff_time']#cutoff time for the question + + #wait some more time before emailing about this question + if emailed_at > cutoff_time: + #here we are maybe losing opportunity to record the finding + #of yet unseen version of a question + meta_data['skip'] = True + continue + + #collect info on all sorts of news that happened after + #the most recent emailing to the user about this question q_rev = QuestionRevision.objects.filter(question=q,\ - revised_at__lt=cutoff_time,\ revised_at__gt=emailed_at) q_rev = q_rev.exclude(author=user) + + #now update all sorts of metadata per question meta_data['q_rev'] = len(q_rev) if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at: meta_data['q_rev'] = 0 @@ -118,21 +193,23 @@ class Command(NoArgsCommand): meta_data['new_q'] = False new_ans = Answer.objects.filter(question=q,\ - added_at__lt=cutoff_time,\ added_at__gt=emailed_at) new_ans = new_ans.exclude(author=user) meta_data['new_ans'] = len(new_ans) ans_rev = AnswerRevision.objects.filter(answer__question=q,\ - revised_at__lt=cutoff_time,\ revised_at__gt=emailed_at) ans_rev = ans_rev.exclude(author=user) meta_data['ans_rev'] = len(ans_rev) - if len(q_rev) == 0 and len(new_ans) == 0 and len(ans_rev) == 0: - meta_data['nothing_new'] = True + + if len(q_rev) + len(new_ans) + len(ans_rev) == 0: + meta_data['skip'] = True else: - meta_data['nothing_new'] = False + meta_data['skip'] = False update_info.active_at = now update_info.save() #save question email update activity + #q_list is actually a ordered dictionary + #print 'user %s gets %d' % (user.username, len(q_list.keys())) + #todo: sort question list by update time return q_list def __action_count(self,string,number,output): @@ -140,17 +217,20 @@ class Command(NoArgsCommand): output.append(_(string) % {'num':number}) def send_email_alerts(self): - + #does not change the database, only sends the email #todo: move this to template for user in User.objects.all(): + #todo: q_list is a dictionary, not a list q_list = self.get_updated_questions_for_user(user) + if len(q_list.keys()) == 0: + continue num_q = 0 num_moot = 0 for meta_data in q_list.values(): - if meta_data['nothing_new'] == False: - num_q += 1 + if meta_data['skip']: + num_moot = True else: - num_moot += 1 + num_q += 1 if num_q > 0: url_prefix = settings.APP_URL subject = _('email update message subject') @@ -160,11 +240,17 @@ class Command(NoArgsCommand): % {'num':num_q, 'name':user.username} text += '<ul>' + items_added = 0 + items_unreported = 0 for q, meta_data in q_list.items(): act_list = [] - if meta_data['nothing_new']: + if meta_data['skip']: continue + if items_added >= const.MAX_ALERTS_PER_EMAIL: + items_unreported = num_q - items_added #may be inaccurate actually, but it's ok + else: + items_added += 1 if meta_data['new_q']: act_list.append(_('new question')) self.__action_count('%(num)d rev', meta_data['q_rev'],act_list) @@ -174,15 +260,52 @@ class Command(NoArgsCommand): text += '<li><a href="%s?sort=latest">%s</a> <font color="#777777">(%s)</font></li>' \ % (url_prefix + q.get_absolute_url(), q.title, act_token) text += '</ul>' - if num_moot > 0: - text += '<p></p>' - text += ungettext('There is also one question which was recently '\ - +'updated but you might not have seen its latest version.', - 'There are also %(num)d more questions which were recently updated '\ - +'but you might not have seen their latest version.',num_moot) \ - % {'num':num_moot,} - text += _('Perhaps you could look up previously sent forum reminders in your mailbox.') - text += '</p>' + text += '<p></p>' + #if len(q_list.keys()) >= const.MAX_ALERTS_PER_EMAIL: + # text += _('There may be more questions updated since ' + # 'you have logged in last time as this list is ' + # 'abridged for your convinience. Please visit ' + # 'the forum and see what\'s new!<br>' + # ) + + text += _( + 'Please visit the forum and see what\'s new! ' + 'Could you spread the word about it - ' + 'can somebody you know help answering those questions or ' + 'benefit from posting one?' + ) + + feeds = EmailFeedSetting.objects.filter( + subscriber=user, + ) + feed_freq = [feed.frequency for feed in feeds] + text += '<p></p>' + if 'd' in feed_freq: + text += _('Your most frequent subscription setting is \'daily\' ' + 'on selected questions. If you are receiving more than one ' + 'email per day' + 'please tell about this issue to the forum administrator.' + ) + elif 'w' in feed_freq: + text += _('Your most frequent subscription setting is \'weekly\' ' + 'if you are receiving this email more than once a week ' + 'please report this issue to the forum administrator.' + ) + text += ' ' + text += _( + 'There is a chance that you may be receiving links seen ' + 'before - due to a technicality that will eventually go away. ' + ) + # text += '</p>' + #if num_moot > 0: + # text += '<p></p>' + # text += ungettext('There is also one question which was recently '\ + # +'updated but you might not have seen its latest version.', + # 'There are also %(num)d more questions which were recently updated '\ + # +'but you might not have seen their latest version.',num_moot) \ + # % {'num':num_moot,} + # text += _('Perhaps you could look up previously sent forum reminders in your mailbox.') + # text += '</p>' link = url_prefix + user.get_profile_url() + '?sort=email_subscriptions' text += _('go to %(link)s to change frequency of email updates or %(email)s administrator') \ @@ -190,3 +313,8 @@ class Command(NoArgsCommand): msg = EmailMessage(subject, text, settings.DEFAULT_FROM_EMAIL, [user.email]) msg.content_subtype = 'html' msg.send() + #uncomment lines below to get copies of emails sent to others + #todo: maybe some debug setting would be appropriate here + #msg2 = EmailMessage(subject, text, settings.DEFAULT_FROM_EMAIL, ['your@email.com']) + #msg2.content_subtype = 'html' + #msg2.send() diff --git a/forum/management/commands/subscribe_everyone.py b/forum/management/commands/subscribe_everyone.py index c79528f3..c79528f3 100644..100755 --- a/forum/management/commands/subscribe_everyone.py +++ b/forum/management/commands/subscribe_everyone.py diff --git a/forum/management/commands/sximport.py b/forum/management/commands/sximport.py new file mode 100755 index 00000000..f6af4e90 --- /dev/null +++ b/forum/management/commands/sximport.py @@ -0,0 +1,109 @@ +from django.core.management.base import LabelCommand +from zipfile import ZipFile +from xml.dom import minidom as dom +import datetime + +from forum.models import User + +class Command(LabelCommand): + def handle_label(self, label, **options): + zip = ZipFile(label) + + map = {} + + map['users'] = self.import_users(zip.open("Users.xml")) + map['questions'], map['answers'] = self.import_posts(zip.open("Posts.xml")) + + + def row_to_dic(self, row): + return dict([ + (child.localName.lower(), + " ".join([t.nodeValue for t in child.childNodes if t.nodeType == t.TEXT_NODE])) + for child in row.childNodes + if child.nodeType == child.ELEMENT_NODE + ]) + + def from_sx_time(self, timestring): + if timestring is None: + return timestring + + try: + return datetime.datetime.strptime(timestring, '%Y-%m-%dT%H:%M:%S') + except: + return datetime.datetime.strptime(timestring, '%Y-%m-%dT%H:%M:%S.%f') + + + def import_users(self, users): + pkey_map = {} + doc = dom.parse(users) + + rows = doc.getElementsByTagName('row') + unknown_count = 0 + + added_names = [] + + for row in rows: + values = self.row_to_dic(row) + + username = values.get('displayname', + values.get('realname', + values.get('email', None))) + + if username is None: + unknown_count += 1 + username = 'Unknown User %d' % unknown_count + + if username in added_names: + cnt = 1 + new_username = "%s %d" % (username, cnt) + while new_username in added_names: + cnt += 1 + new_username = "%s %d" % (username, cnt) + + username = new_username + + added_names.append(username) + + user = User(username=username, email=values.get('email', '')) + + user.reputation = values['reputation'] + user.last_seen = self.from_sx_time(values['lastaccessdate']) + + user.real_name = values.get('realname', '') + user.about = values.get('aboutme', '') + user.website = values.get('websiteurl', '') + user.date_of_birth = self.from_sx_time(values.get('birthday', None)) + user.location = values.get('location', '') + + user.is_active = True + user.email_isvalid = True + + + if int(values['usertypeid']) == 5: + user.is_superuser = True + + if int(values['usertypeid']) == 5: + user.is_staff = True + + user.save() + + pkey_map[values['id']] = user + + return users + + def import_posts(self, posts, map): + pkey_map = {} + doc = dom.parse(posts) + + rows = doc.getElementsByTagName('row') + + for row in rows: + map = { + 'title': row[''] + } + + pass + pass + + + diff --git a/forum/middleware/__init__.py b/forum/middleware/__init__.py index e69de29b..e69de29b 100644..100755 --- a/forum/middleware/__init__.py +++ b/forum/middleware/__init__.py diff --git a/forum/middleware/anon_user.py b/forum/middleware/anon_user.py index e05e6f33..866734da 100644..100755 --- a/forum/middleware/anon_user.py +++ b/forum/middleware/anon_user.py @@ -3,6 +3,7 @@ from forum.utils.forms import get_next_url from django.utils.translation import ugettext as _ from forum.user_messages import create_message, get_and_delete_messages from django.conf import settings +from django.core.urlresolvers import reverse import logging class AnonymousMessageManager(object): @@ -30,5 +31,5 @@ class ConnectToSessionMessagesMiddleware(object): #also set the first greeting one time per session only if 'greeting_set' not in request.session: request.session['greeting_set'] = True - msg = _('first time greeting with %(url)s') % {'url':settings.GREETING_URL} + msg = _('First time here? Check out the <a href="%s">FAQ</a>!') % reverse('faq') request.user.message_set.create(message=msg) diff --git a/forum/middleware/cancel.py b/forum/middleware/cancel.py index 15a4371d..15a4371d 100644..100755 --- a/forum/middleware/cancel.py +++ b/forum/middleware/cancel.py diff --git a/forum/middleware/pagesize.py b/forum/middleware/pagesize.py index f6e6fcfd..f6e6fcfd 100644..100755 --- a/forum/middleware/pagesize.py +++ b/forum/middleware/pagesize.py diff --git a/forum/models/__init__.py b/forum/models/__init__.py index 9b504103..f4850025 100755 --- a/forum/models/__init__.py +++ b/forum/models/__init__.py @@ -2,10 +2,25 @@ from question import Question ,QuestionRevision, QuestionView, AnonymousQuestion from answer import Answer, AnonymousAnswer, AnswerRevision from tag import Tag, MarkedTag from meta import Vote, Comment, FlaggedItem -from user import Activity, AnonymousEmail, EmailFeedSetting +from user import Activity, ValidationHash, EmailFeedSetting, AuthKeyUserAssociation from repute import Badge, Award, Repute +import re from base import * +import datetime +from django.contrib.contenttypes.models import ContentType + +#todo: move to a separate file? +# custom signals +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"]) + +#todo: must go after signals +from forum import auth # User extend properties QUESTIONS_PER_PAGE_CHOICES = ( @@ -33,6 +48,9 @@ def user_get_q_sel_email_feed_frequency(self): #print 'have freq=%s' % feed_setting.frequency return feed_setting.frequency +def user_get_absolute_url(self): + return "/users/%d/%s/" % (self.id, (self.username)) + 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)) @@ -67,15 +85,7 @@ User.add_to_class('tag_filter_setting', 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"]) - +User.add_to_class('get_absolute_url', user_get_absolute_url) def get_messages(self): messages = [] @@ -88,13 +98,133 @@ def delete_messages(self): def get_profile_url(self): """Returns the URL for this User's profile.""" - return '%s%s/' % (reverse('user', args=[self.id]), slugify(self.username)) + return "/users/%d/%s" % (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) +#series of methods for user vote-type commands +#same call signature func(self, post, timestamp=None, cancel=None) +#note that none of these have business logic checks internally +#these functions are used by the forum app and +#by the data importer jobs from say stackexchange, where internal rules +#may be different +#maybe if we do use business rule checks here - we should add +#some flag allowing to bypass them for things like the data importers +def toggle_favorite_question(self, question, timestamp=None, cancel=False): + """cancel has no effect here, but is important for the SE loader + it is hoped that toggle will work and data will be consistent + but there is no guarantee, maybe it's better to be more strict + about processing the "cancel" option + another strange thing is that this function unlike others below + returns a value + """ + try: + fave = FavoriteQuestion.objects.get(question=question, user=self) + fave.delete() + result = False + except FavoriteQuestion.DoesNotExist: + fave = FavoriteQuestion( + question = question, + user = self, + added_at = timestamp, + ) + fave.save() + result = True + Question.objects.update_favorite_count(question) + return result + +#"private" wrapper function that applies post upvotes/downvotes and cancelations +def _process_vote(user, post, timestamp=None, cancel=False, vote_type=None): + post_type = ContentType.objects.get_for_model(post) + #get or create the vote object + #return with noop in some situations + try: + vote = Vote.objects.get( + user = user, + content_type = post_type, + object_id = post.id, + ) + except Vote.DoesNotExist: + vote = None + if cancel: + if vote == None: + return + elif vote.is_opposite(vote_type): + return + else: + #we would call vote.delete() here + #but for now all that is handled by the + #legacy forum.auth functions + #vote.delete() + pass + else: + if vote == None: + vote = Vote( + user = user, + content_object = post, + vote = vote_type, + voted_at = timestamp, + ) + elif vote.is_opposite(vote_type): + vote.vote = vote_type + else: + return + + #do the actual work + if vote_type == Vote.VOTE_UP: + if cancel: + auth.onUpVotedCanceled(vote, post, user, timestamp) + else: + auth.onUpVoted(vote, post, user, timestamp) + elif vote_type == Vote.VOTE_DOWN: + if cancel: + auth.onDownVotedCanceled(vote, post, user, timestamp) + else: + auth.onDonwVoted(vote, post, user, timestamp) + +def upvote(self, post, timestamp=None, cancel=False): + _process_vote( + self,post, + timestamp=timestamp, + cancel=cancel, + vote_type=Vote.VOTE_UP + ) + +def downvote(self, post, timestamp=None, cancel=False): + _process_vote( + self,post, + timestamp=timestamp, + cancel=cancel, + vote_type=Vote.VOTE_DOWN + ) + +def accept_answer(self, answer, timestamp=None, cancel=False): + if cancel: + auth.onAnswerAcceptCanceled(answer, self, timestamp=timestamp) + else: + auth.onAnswerAccept(answer, self, timestamp=timestamp) + +def flag_post(self, post, timestamp=None, cancel=False): + if cancel:#todo: can't unflag? + return + if post.flagged_items.filter(user=user).count() > 0: + return + else: + flag = FlaggedItem( + user = self, + content_object = post, + flagged_at = timestamp, + ) + auth.onFlaggedItem(flag, post, user, timestamp=timestamp) + +User.add_to_class('toggle_favorite_question', toggle_favorite_question) +User.add_to_class('upvote', upvote) +User.add_to_class('downvote', downvote) +User.add_to_class('accept_answer', accept_answer) +User.add_to_class('flag_post', flag_post) 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) @@ -111,9 +241,33 @@ def record_ask_event(instance, created, **kwargs): activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION) activity.save() +#todo: translate this +record_answer_event_re = re.compile("You have received (a|\d+) .*new response.*") 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) + q_author = instance.question.author + found_match = False + for m in q_author.message_set.all(): + match = record_answer_event_re.search(m.message) + if match: + found_match = True + try: + cnt = int(match.group(1)) + except: + cnt = 1 + m.message = u"You have received %d <a href=\"%s?sort=responses\">new responses</a>."\ + % (cnt+1, q_author.get_profile_url()) + m.save() + break + if not found_match: + msg = u"You have received a <a href=\"%s?sort=responses\">new response</a>."\ + % q_author.get_profile_url() + q_author.message_set.create(message=msg) + + 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): @@ -165,7 +319,12 @@ def notify_award_message(instance, created, **kwargs): """ if created: user = instance.user - user.message_set.create(message=u"Congratulations, you have received a badge '%s'" % instance.badge.name) + + msg = (u"Congratulations, you have received a badge '%s'. " \ + + u"Check out <a href=\"%s\">your profile</a>.") \ + % (instance.badge.name, user.get_profile_url()) + + user.message_set.create(message=msg) def record_answer_accepted(instance, created, **kwargs): """ @@ -258,7 +417,6 @@ def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs) 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) @@ -302,7 +460,8 @@ Repute = Repute Activity = Activity EmailFeedSetting = EmailFeedSetting -AnonymousEmail = AnonymousEmail +ValidationHash = ValidationHash +AuthKeyUserAssociation = AuthKeyUserAssociation __all__ = [ 'Question', @@ -327,9 +486,10 @@ __all__ = [ 'Activity', 'EmailFeedSetting', - 'AnonymousEmail', + 'ValidationHash', + 'AuthKeyUserAssociation', - 'User' + 'User', ] @@ -338,4 +498,4 @@ from forum.modules import get_modules_script_classes for k, v in get_modules_script_classes('models', models.Model).items(): if not k in __all__: __all__.append(k) - exec "%s = v" % k
\ No newline at end of file + exec "%s = v" % k diff --git a/forum/models/answer.py b/forum/models/answer.py index 615c7cc0..a41069f5 100755 --- a/forum/models/answer.py +++ b/forum/models/answer.py @@ -16,7 +16,7 @@ class AnswerManager(models.Manager): author = author, added_at = added_at, wiki = wiki, - html = sanitize_html(markdowner.convert(text)) + html = sanitize_html(markdowner.convert(text)), ) if answer.wiki: answer.last_edited_by = answer.author @@ -25,21 +25,19 @@ class AnswerManager(models.Manager): answer.save() + answer.add_revision( + revised_by=author, + revised_at=added_at, + text=text, + comment=CONST['default_version'], + ) + #update question data question.last_activity_at = added_at question.last_activity_by = author question.save() Question.objects.update_answer_count(question) - AnswerRevision.objects.create( - answer = answer, - revision = 1, - author = author, - revised_at = added_at, - summary = CONST['default_version'], - text = text - ) - #set notification/delete if email_notify: if author not in question.followed_by.all(): @@ -84,6 +82,50 @@ class Answer(Content, DeletableContent): class Meta(Content.Meta): db_table = u'answer' + def apply_edit(self, edited_at=None, edited_by=None, text=None, comment=None, wiki=False): + + if text is None: + text = self.get_latest_revision().text + if edited_at is None: + edited_at = datetime.datetime.now() + if edited_by is None: + raise Exception('edited_by is required') + + self.last_edited_at = edited_at + self.last_edited_by = edited_by + self.html = sanitize_html(markdowner.convert(text)) + #todo: bug wiki has no effect here + self.save() + + self.add_revision( + revised_by=edited_by, + revised_at=edited_at, + text=text, + comment=comment + ) + + self.question.last_activity_at = edited_at + self.question.last_activity_by = edited_by + self.question.save() + + def add_revision(self, revised_by=None, revised_at=None, text=None, comment=None): + if None in (revised_by, revised_at, text): + raise Exception('arguments revised_by, revised_at and text are required') + rev_no = self.revisions.all().count() + 1 + if comment in (None, ''): + if rev_no == 1: + comment = CONST['default_version'] + else: + comment = 'No.%s Revision' % rev_no + return AnswerRevision.objects.create( + answer=self, + author=revised_by, + revised_at=revised_at, + text=text, + summary=comment, + revision=rev_no + ) + def get_user_vote(self, user): if user.__class__.__name__ == "AnonymousUser": return None diff --git a/forum/models/base.py b/forum/models/base.py index 49a7545d..fb66ff1b 100755 --- a/forum/models/base.py +++ b/forum/models/base.py @@ -17,11 +17,15 @@ import django.dispatch from django.conf import settings import logging -if settings.USE_SPHINX_SEARCH == True: - from djangosphinx.models import SphinxSearch - from forum.const import * +class UserContent(models.Model): + user = models.ForeignKey(User, related_name='%(class)ss') + + class Meta: + abstract = True + app_label = 'forum' + class MetaContent(models.Model): """ Base class for Vote, Comment and FlaggedItem @@ -29,7 +33,6 @@ class MetaContent(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='%(class)ss') class Meta: abstract = True @@ -124,6 +127,18 @@ class Content(models.Model): comments = self.comments.all().order_by('id') return comments + def add_comment(self, comment=None, user=None, added_at=None): + if added_at is None: + added_at = datetime.datetime.now() + if None in (comment ,user): + raise Exception('arguments comment and user are required') + + Comment = models.get_model('forum','Comment')#todo: forum hardcoded + comment = Comment(content_object=self, comment=comment, user=user, added_at=added_at) + comment.save() + self.comment_count = self.comment_count + 1 + self.save() + def post_get_last_update_info(self): when = self.added_at who = self.author diff --git a/forum/models/meta.py b/forum/models/meta.py index 7c3f5d36..114d2130 100755 --- a/forum/models/meta.py +++ b/forum/models/meta.py @@ -22,7 +22,7 @@ class VoteManager(models.Manager): return 0 -class Vote(MetaContent): +class Vote(MetaContent, UserContent): VOTE_UP = +1 VOTE_DOWN = -1 VOTE_CHOICES = ( @@ -48,6 +48,10 @@ class Vote(MetaContent): def is_downvote(self): return self.vote == self.VOTE_DOWN + def is_opposite(self, vote_type): + assert(vote_type in (self.VOTE_UP, self.VOTE_DOWN)) + return self.vote != vote_type + class FlaggedItemManager(models.Manager): def get_flagged_items_count_today(self, user): @@ -57,7 +61,7 @@ class FlaggedItemManager(models.Manager): else: return 0 -class FlaggedItem(MetaContent): +class FlaggedItem(MetaContent, UserContent): """A flag on a Question or Answer indicating offensive content.""" flagged_at = models.DateTimeField(default=datetime.datetime.now) @@ -70,7 +74,7 @@ class FlaggedItem(MetaContent): def __unicode__(self): return '[%s] flagged at %s' %(self.user, self.flagged_at) -class Comment(MetaContent): +class Comment(MetaContent, UserContent): comment = models.CharField(max_length=300) added_at = models.DateTimeField(default=datetime.datetime.now) @@ -86,4 +90,4 @@ class Comment(MetaContent): logging.debug('problem pinging google did you register you sitemap with google?') def __unicode__(self): - return self.comment
\ No newline at end of file + return self.comment diff --git a/forum/models/question.py b/forum/models/question.py index 7ad17555..8f59c448 100755 --- a/forum/models/question.py +++ b/forum/models/question.py @@ -1,15 +1,18 @@ from base import * from tag import Tag -#todo: take care of copy-paste markdowner stuff maybe make html automatic field? from forum.const import CONST -from markdown2 import Markdown -from django.utils.html import strip_tags from forum.utils.html import sanitize_html +from markdown2 import Markdown +from django.utils.html import strip_tags import datetime markdowner = Markdown(html4tags=True) +from forum.utils.lists import LazyList + class QuestionManager(models.Manager): - def create_new(self, title=None,author=None,added_at=None, wiki=False,tagnames=None,summary=None, text=None): + def create_new(cls, title=None,author=None,added_at=None, wiki=False,tagnames=None, text=None): + html = sanitize_html(markdowner.convert(text)) + summary = strip_tags(html)[:120] question = Question( title = title, author = author, @@ -18,7 +21,7 @@ class QuestionManager(models.Manager): last_activity_by = author, wiki = wiki, tagnames = tagnames, - html = sanitize_html(markdowner.convert(text)), + html = html, summary = summary ) if question.wiki: @@ -28,16 +31,11 @@ class QuestionManager(models.Manager): question.save() - # create the first revision - QuestionRevision.objects.create( - question = question, - revision = 1, - title = question.title, - author = author, - revised_at = added_at, - tagnames = question.tagnames, - summary = CONST['default_version'], - text = text + question.add_revision( + author=author, + text=text, + comment=CONST['default_version'], + revised_at=added_at, ) return question @@ -74,6 +72,9 @@ class QuestionManager(models.Manager): return False + #todo: why not make this into a method of class Question? + # also it is actually strange - why do we need the answer_count + # field if the count depends on who is requesting this? def update_answer_count(self, question): """ Executes an UPDATE query to update denormalised data with the @@ -105,17 +106,25 @@ class QuestionManager(models.Manager): 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) + manager = self + + def get_data(): + questions = list(manager.filter(tagnames = question.tagnames, deleted=False).all()) + + tags_list = question.tags.all() + for tag in tags_list: + extend_questions = manager.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 + + return LazyList(get_data) + - #print datetime.datetime.now() - return questions class Question(Content, DeletableContent): title = models.CharField(max_length=300) @@ -150,6 +159,105 @@ class Question(Content, DeletableContent): except Exception: logging.debug('problem pinging google did you register you sitemap with google?') + def retag(self, retagged_by=None, retagged_at=None, tagnames=None): + if None in (retagged_by, retagged_at, tagnames): + raise Exception('arguments retagged_at, retagged_by and tagnames are required') + # Update the Question itself + self.tagnames = tagnames + self.last_edited_at = retagged_at + self.last_activity_at = retagged_at + self.last_edited_by = retagged_by + self.last_activity_by = retagged_by + + # Update the Question's tag associations + tags_updated = self.objects.update_tags(self, + form.cleaned_data['tags'], request.user) + + # Create a new revision + latest_revision = self.get_latest_revision() + QuestionRevision.objects.create( + question = self, + title = latest_revision.title, + author = retagged_by, + revised_at = retagged_at, + tagnames = tagnames, + summary = CONST['retagged'], + text = latest_revision.text + ) + # send tags updated singal + tags_updated.send(sender=question.__class__, question=self) + + def apply_edit(self, edited_at=None, edited_by=None, title=None,\ + text=None, comment=None, tags=None, wiki=False): + + latest_revision = self.get_latest_revision() + #a hack to allow partial edits - important for SE loader + if title is None: + title = self.title + if text is None: + text = latest_revision.text + if tags is None: + tags = latest_revision.tagnames + + if edited_by is None: + raise Exception('parameter edited_by is required') + + if edited_at is None: + edited_at = datetime.datetime.now() + + #todo: have this copy-paste in few places + html = sanitize_html(markdowner.convert(text)) + question_summary = strip_tags(html)[:120] + + # Update the Question itself + self.title = title + self.last_edited_at = edited_at + self.last_activity_at = edited_at + self.last_edited_by = edited_by + self.last_activity_by = edited_by + self.tagnames = tags + self.summary = question_summary + self.html = html + + #wiki is an eternal trap whence there is no exit + if self.wiki == False and wiki == True: + self.wiki = True + + self.save() + + # Update the Question tag associations + if latest_revision.tagnames != tags: + tags_updated = Question.objects.update_tags(self, tags, edited_by) + + # Create a new revision + self.add_revision( + author = edited_by, + text = text, + revised_at = edited_at, + comment = comment, + ) + + def add_revision(self,author=None, text=None, comment=None, revised_at=None): + if None in (author, text, comment): + raise Exception('author, text and revised_at are required arguments') + rev_no = self.revisions.all().count() + 1 + if comment in (None, ''): + if rev_no == 1: + comment = CONST['default_version'] + else: + comment = 'No.%s Revision' % rev_no + + return QuestionRevision.objects.create( + question = self, + revision = rev_no, + title = self.title, + author = author, + revised_at = revised_at, + tagnames = self.tagnames, + summary = comment, + text = text + ) + def save(self, **kwargs): """ Overridden to manually manage addition of tags when the object @@ -335,6 +443,7 @@ class AnonymousQuestion(AnonymousContent): def publish(self,user): added_at = datetime.datetime.now() qm = QuestionManager() + #todo: use as classmethod instead?? qm.create_new(title=self.title, author=user, added_at=added_at, wiki=self.wiki, tagnames=self.tagnames, summary=self.summary, text=self.text) diff --git a/forum/models/repute.py b/forum/models/repute.py index 42bbe476..65561d68 100755 --- a/forum/models/repute.py +++ b/forum/models/repute.py @@ -1,4 +1,6 @@ from base import * +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import User from django.utils.translation import ugettext as _ @@ -79,15 +81,26 @@ class ReputeManager(models.Manager): 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))). \ - aggregate(models.Sum('positive'), models.Sum('negative')) - - return sums['positive__sum'] + sums['negative__sum'] - else: + if user is None: return 0 + else: + today = datetime.date.today() + tomorrow = today + datetime.timedelta(1) + rep_types = (1,-8) + sums = self.filter(models.Q(reputation_type__in=rep_types), + user=user, + reputed_at__range=(today, tomorrow), + ).aggregate(models.Sum('positive'), models.Sum('negative')) + if sums: + pos = sums['positive__sum'] + neg = sums['negative__sum'] + if pos is None: + pos = 0 + if neg is None: + neg = 0 + return pos + neg + else: + return 0 class Repute(models.Model): """The reputation histories for user""" diff --git a/forum/models/user.py b/forum/models/user.py index ac6cbc0d..6d871bf4 100755 --- a/forum/models/user.py +++ b/forum/models/user.py @@ -1,4 +1,9 @@ from base import * +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import User +from hashlib import md5 +import string +from random import Random from django.utils.translation import ugettext as _ @@ -55,13 +60,75 @@ class EmailFeedSetting(models.Model): 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) +from forum.utils.time import one_day_from_now + +class ValidationHashManager(models.Manager): + def _generate_md5_hash(self, user, type, hash_data, seed): + return md5("%s%s%s%s" % (seed, "".join(map(str, hash_data)), user.id, type)).hexdigest() + + def create_new(self, user, type, hash_data=[], expiration=None): + seed = ''.join(Random().sample(string.letters+string.digits, 12)) + hash = self._generate_md5_hash(user, type, hash_data, seed) + + obj = ValidationHash(hash_code=hash, seed=seed, user=user, type=type) + + if expiration is not None: + obj.expiration = expiration + + try: + obj.save() + except: + return None + + return obj + + def validate(self, hash, user, type, hash_data=[]): + try: + obj = self.get(hash_code=hash) + except: + return False + + if obj.type != type: + return False + + if obj.user != user: + return False + + valid = (obj.hash_code == self._generate_md5_hash(obj.user, type, hash_data, obj.seed)) + + if valid: + if obj.expiration < datetime.datetime.now(): + obj.delete() + return False + else: + obj.delete() + return True + + return False + +class ValidationHash(models.Model): + #todo: was 256 chars - is that important? + #on mysql 255 is max for unique=True + hash_code = models.CharField(max_length=255,unique=True) + seed = models.CharField(max_length=12) + expiration = models.DateTimeField(default=one_day_from_now) + type = models.CharField(max_length=12) + user = models.ForeignKey(User) + + objects = ValidationHashManager() class Meta: + unique_together = ('user', 'type') app_label = 'forum' + def __str__(self): + return self.hash_code + +class AuthKeyUserAssociation(models.Model): + key = models.CharField(max_length=255,null=False,unique=True) + provider = models.CharField(max_length=64)#string 'yahoo', 'google', etc. + user = models.ForeignKey(User, related_name="auth_keys") + added_at = models.DateTimeField(default=datetime.datetime.now) + class Meta: + app_label = 'forum' diff --git a/forum/modules.py b/forum/modules.py index 26c4d50a..6c9a9dba 100755 --- a/forum/modules.py +++ b/forum/modules.py @@ -1,5 +1,8 @@ import os import types +import re + +from django.template import Template, TemplateDoesNotExist MODULES_PACKAGE = 'forum_modules' @@ -19,7 +22,8 @@ def get_modules_script(script_name): for m in MODULE_LIST: try: all.append(__import__('%s.%s' % (m.__name__, script_name), globals(), locals(), [m.__name__])) - except: + except Exception, e: + #print script_name + ":" + str(e) pass return all @@ -50,5 +54,25 @@ def get_all_handlers(name): def get_handler(name, default): all = get_all_handlers(name) - print(len(all)) - return len(all) and all[0] or default
\ No newline at end of file + return len(all) and all[0] or default + +module_template_re = re.compile('^modules\/(\w+)\/(.*)$') + +def module_templates_loader(name, dirs=None): + result = module_template_re.search(name) + + if result is not None: + file_name = os.path.join(MODULES_FOLDER, result.group(1), 'templates', result.group(2)) + + if os.path.exists(file_name): + try: + f = open(file_name, 'r') + source = f.read() + f.close() + return (source, file_name) + except: + pass + + raise TemplateDoesNotExist, name + +module_templates_loader.is_usable = True diff --git a/forum/settings.py b/forum/settings.py new file mode 100755 index 00000000..04a7c399 --- /dev/null +++ b/forum/settings.py @@ -0,0 +1,51 @@ +import os + + +INSTALLED_APPS = ['forum'] + +MIDDLEWARE_CLASSES = [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'forum.middleware.anon_user.ConnectToSessionMessagesMiddleware', + 'forum.middleware.pagesize.QuestionsPageSizeMiddleware', + 'forum.middleware.cancel.CancelActionMiddleware', + 'django.middleware.transaction.TransactionMiddleware', +] + +TEMPLATE_LOADERS = [ + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', + 'forum.modules.module_templates_loader', + 'forum.skins.load_template_source', +] + +TEMPLATE_CONTEXT_PROCESSORS = [ + 'django.core.context_processors.request', + 'forum.context.application_settings', + 'forum.user_messages.context_processors.user_messages', + 'django.core.context_processors.auth', +] + +TEMPLATE_DIRS = [ + os.path.join(os.path.dirname(__file__),'skins').replace('\\','/'), +] + +def setup_settings(settings): + + if (hasattr(settings, 'DEBUG') and getattr(settings, 'DEBUG')): + try: + import debug_toolbar + INSTALLED_APPS.append('debug_toolbar') + MIDDLEWARE_CLASSES.append('debug_toolbar.middleware.DebugToolbarMiddleware') + except: + pass + + + settings.INSTALLED_APPS = set(settings.INSTALLED_APPS) | set(INSTALLED_APPS) + settings.MIDDLEWARE_CLASSES = set(settings.MIDDLEWARE_CLASSES) | set(MIDDLEWARE_CLASSES) + settings.TEMPLATE_LOADERS = set(settings.TEMPLATE_LOADERS) | set(TEMPLATE_LOADERS) + settings.TEMPLATE_CONTEXT_PROCESSORS = set(settings.TEMPLATE_CONTEXT_PROCESSORS) | set(TEMPLATE_CONTEXT_PROCESSORS) + settings.TEMPLATE_DIRS = set(settings.TEMPLATE_DIRS) | set(TEMPLATE_DIRS) + +
\ No newline at end of file diff --git a/forum/sitemap.py b/forum/sitemap.py index c0c60b5e..c0c60b5e 100644..100755 --- a/forum/sitemap.py +++ b/forum/sitemap.py diff --git a/forum/skins/README b/forum/skins/README index 5565fa83..5565fa83 100644..100755 --- a/forum/skins/README +++ b/forum/skins/README diff --git a/forum/skins/__init__.py b/forum/skins/__init__.py index be6bd4f3..be6bd4f3 100644..100755 --- a/forum/skins/__init__.py +++ b/forum/skins/__init__.py diff --git a/forum/skins/common/media/README b/forum/skins/common/media/README index 3376e754..3376e754 100644..100755 --- a/forum/skins/common/media/README +++ b/forum/skins/common/media/README diff --git a/forum/skins/default/media/images/blue-up-arrow-h18px.png b/forum/skins/default/media/images/blue-up-arrow-h18px.png Binary files differindex e1f29e86..e1f29e86 100644..100755 --- a/forum/skins/default/media/images/blue-up-arrow-h18px.png +++ b/forum/skins/default/media/images/blue-up-arrow-h18px.png diff --git a/forum/skins/default/media/images/box-arrow.gif b/forum/skins/default/media/images/box-arrow.gif Binary files differindex 89dcf5b3..89dcf5b3 100644..100755 --- a/forum/skins/default/media/images/box-arrow.gif +++ b/forum/skins/default/media/images/box-arrow.gif diff --git a/forum/skins/default/media/images/bullet_green.gif b/forum/skins/default/media/images/bullet_green.gif Binary files differindex fa530910..fa530910 100644..100755 --- a/forum/skins/default/media/images/bullet_green.gif +++ b/forum/skins/default/media/images/bullet_green.gif diff --git a/forum/skins/default/media/images/cc-88x31.png b/forum/skins/default/media/images/cc-88x31.png Binary files differindex 0f2a0f10..0f2a0f10 100644..100755 --- a/forum/skins/default/media/images/cc-88x31.png +++ b/forum/skins/default/media/images/cc-88x31.png diff --git a/forum/skins/default/media/images/cc-wiki.png b/forum/skins/default/media/images/cc-wiki.png Binary files differindex 3e680538..3e680538 100644..100755 --- a/forum/skins/default/media/images/cc-wiki.png +++ b/forum/skins/default/media/images/cc-wiki.png diff --git a/forum/skins/default/media/images/close-small-dark.png b/forum/skins/default/media/images/close-small-dark.png Binary files differindex 280c1fc7..280c1fc7 100644..100755 --- a/forum/skins/default/media/images/close-small-dark.png +++ b/forum/skins/default/media/images/close-small-dark.png diff --git a/forum/skins/default/media/images/close-small-hover.png b/forum/skins/default/media/images/close-small-hover.png Binary files differindex 7899aec7..7899aec7 100644..100755 --- a/forum/skins/default/media/images/close-small-hover.png +++ b/forum/skins/default/media/images/close-small-hover.png diff --git a/forum/skins/default/media/images/close-small.png b/forum/skins/default/media/images/close-small.png Binary files differindex 5a99d31f..5a99d31f 100644..100755 --- a/forum/skins/default/media/images/close-small.png +++ b/forum/skins/default/media/images/close-small.png diff --git a/forum/skins/default/media/images/dash.gif b/forum/skins/default/media/images/dash.gif Binary files differindex d1ddc507..d1ddc507 100644..100755 --- a/forum/skins/default/media/images/dash.gif +++ b/forum/skins/default/media/images/dash.gif diff --git a/forum/skins/default/media/images/djangomade124x25_grey.gif b/forum/skins/default/media/images/djangomade124x25_grey.gif Binary files differindex d34bb311..d34bb311 100644..100755 --- a/forum/skins/default/media/images/djangomade124x25_grey.gif +++ b/forum/skins/default/media/images/djangomade124x25_grey.gif diff --git a/forum/skins/default/media/images/dot-g.gif b/forum/skins/default/media/images/dot-g.gif Binary files differindex 5d6bb28e..5d6bb28e 100644..100755 --- a/forum/skins/default/media/images/dot-g.gif +++ b/forum/skins/default/media/images/dot-g.gif diff --git a/forum/skins/default/media/images/dot-list.gif b/forum/skins/default/media/images/dot-list.gif Binary files differindex f6a6b865..f6a6b865 100644..100755 --- a/forum/skins/default/media/images/dot-list.gif +++ b/forum/skins/default/media/images/dot-list.gif diff --git a/forum/skins/default/media/images/edit.png b/forum/skins/default/media/images/edit.png Binary files differindex dcb09be0..dcb09be0 100644..100755 --- a/forum/skins/default/media/images/edit.png +++ b/forum/skins/default/media/images/edit.png diff --git a/forum/skins/default/media/images/expander-arrow-hide.gif b/forum/skins/default/media/images/expander-arrow-hide.gif Binary files differindex feb6a618..feb6a618 100644..100755 --- a/forum/skins/default/media/images/expander-arrow-hide.gif +++ b/forum/skins/default/media/images/expander-arrow-hide.gif diff --git a/forum/skins/default/media/images/expander-arrow-show.gif b/forum/skins/default/media/images/expander-arrow-show.gif Binary files differindex 6825c56e..6825c56e 100644..100755 --- a/forum/skins/default/media/images/expander-arrow-show.gif +++ b/forum/skins/default/media/images/expander-arrow-show.gif diff --git a/forum/skins/default/media/images/favicon.ico b/forum/skins/default/media/images/favicon.ico Binary files differnew file mode 100644 index 00000000..35c9e149 --- /dev/null +++ b/forum/skins/default/media/images/favicon.ico diff --git a/forum/skins/default/media/images/feed-icon-small.png b/forum/skins/default/media/images/feed-icon-small.png Binary files differindex b3c949d2..b3c949d2 100644..100755 --- a/forum/skins/default/media/images/feed-icon-small.png +++ b/forum/skins/default/media/images/feed-icon-small.png diff --git a/forum/skins/default/media/images/gray-up-arrow-h18px.png b/forum/skins/default/media/images/gray-up-arrow-h18px.png Binary files differindex 78767445..78767445 100644..100755 --- a/forum/skins/default/media/images/gray-up-arrow-h18px.png +++ b/forum/skins/default/media/images/gray-up-arrow-h18px.png diff --git a/forum/skins/default/media/images/grippie.png b/forum/skins/default/media/images/grippie.png Binary files differindex 6524d416..6524d416 100644..100755 --- a/forum/skins/default/media/images/grippie.png +++ b/forum/skins/default/media/images/grippie.png diff --git a/forum/skins/default/media/images/indicator.gif b/forum/skins/default/media/images/indicator.gif Binary files differindex 1c72ebb5..1c72ebb5 100644..100755 --- a/forum/skins/default/media/images/indicator.gif +++ b/forum/skins/default/media/images/indicator.gif diff --git a/forum/skins/default/media/images/logo1.png b/forum/skins/default/media/images/logo1.png Binary files differindex d79a6271..d79a6271 100644..100755 --- a/forum/skins/default/media/images/logo1.png +++ b/forum/skins/default/media/images/logo1.png diff --git a/forum/skins/default/media/images/logo2.png b/forum/skins/default/media/images/logo2.png Binary files differindex bd3cccd9..bd3cccd9 100644..100755 --- a/forum/skins/default/media/images/logo2.png +++ b/forum/skins/default/media/images/logo2.png diff --git a/forum/skins/default/media/images/medala.gif b/forum/skins/default/media/images/medala.gif Binary files differindex 93dd1a39..93dd1a39 100644..100755 --- a/forum/skins/default/media/images/medala.gif +++ b/forum/skins/default/media/images/medala.gif diff --git a/forum/skins/default/media/images/medala_on.gif b/forum/skins/default/media/images/medala_on.gif Binary files differindex a18f9e85..a18f9e85 100644..100755 --- a/forum/skins/default/media/images/medala_on.gif +++ b/forum/skins/default/media/images/medala_on.gif diff --git a/forum/skins/default/media/images/new.gif b/forum/skins/default/media/images/new.gif Binary files differindex 8a220b53..8a220b53 100644..100755 --- a/forum/skins/default/media/images/new.gif +++ b/forum/skins/default/media/images/new.gif diff --git a/forum/skins/default/media/images/nophoto.png b/forum/skins/default/media/images/nophoto.png Binary files differindex 2daf0ffd..2daf0ffd 100644..100755 --- a/forum/skins/default/media/images/nophoto.png +++ b/forum/skins/default/media/images/nophoto.png diff --git a/forum/skins/default/media/images/openid.gif b/forum/skins/default/media/images/openid.gif Binary files differindex 8540e12b..8540e12b 100644..100755 --- a/forum/skins/default/media/images/openid.gif +++ b/forum/skins/default/media/images/openid.gif diff --git a/forum/skins/default/media/images/openid/aol.gif b/forum/skins/default/media/images/openid/aol.gif Binary files differindex decc4f12..decc4f12 100644..100755 --- a/forum/skins/default/media/images/openid/aol.gif +++ b/forum/skins/default/media/images/openid/aol.gif diff --git a/forum/skins/default/media/images/openid/blogger.ico b/forum/skins/default/media/images/openid/blogger.ico Binary files differindex 1b9730b0..1b9730b0 100644..100755 --- a/forum/skins/default/media/images/openid/blogger.ico +++ b/forum/skins/default/media/images/openid/blogger.ico diff --git a/forum/skins/default/media/images/openid/claimid.ico b/forum/skins/default/media/images/openid/claimid.ico Binary files differindex 2b80f491..2b80f491 100644..100755 --- a/forum/skins/default/media/images/openid/claimid.ico +++ b/forum/skins/default/media/images/openid/claimid.ico diff --git a/forum/skins/default/media/images/openid/facebook.gif b/forum/skins/default/media/images/openid/facebook.gif Binary files differindex b997b358..b997b358 100644..100755 --- a/forum/skins/default/media/images/openid/facebook.gif +++ b/forum/skins/default/media/images/openid/facebook.gif diff --git a/forum/skins/default/media/images/openid/flickr.ico b/forum/skins/default/media/images/openid/flickr.ico Binary files differindex 11f6e07f..11f6e07f 100644..100755 --- a/forum/skins/default/media/images/openid/flickr.ico +++ b/forum/skins/default/media/images/openid/flickr.ico diff --git a/forum/skins/default/media/images/openid/google.gif b/forum/skins/default/media/images/openid/google.gif Binary files differindex 1b6cd07b..1b6cd07b 100644..100755 --- a/forum/skins/default/media/images/openid/google.gif +++ b/forum/skins/default/media/images/openid/google.gif diff --git a/forum/skins/default/media/images/openid/livejournal.ico b/forum/skins/default/media/images/openid/livejournal.ico Binary files differindex f3d21ec5..f3d21ec5 100644..100755 --- a/forum/skins/default/media/images/openid/livejournal.ico +++ b/forum/skins/default/media/images/openid/livejournal.ico diff --git a/forum/skins/default/media/images/openid/myopenid.ico b/forum/skins/default/media/images/openid/myopenid.ico Binary files differindex ceb06e6a..ceb06e6a 100644..100755 --- a/forum/skins/default/media/images/openid/myopenid.ico +++ b/forum/skins/default/media/images/openid/myopenid.ico diff --git a/forum/skins/default/media/images/openid/openid-inputicon.gif b/forum/skins/default/media/images/openid/openid-inputicon.gif Binary files differindex cde836c8..cde836c8 100644..100755 --- a/forum/skins/default/media/images/openid/openid-inputicon.gif +++ b/forum/skins/default/media/images/openid/openid-inputicon.gif diff --git a/forum/skins/default/media/images/openid/openid.gif b/forum/skins/default/media/images/openid/openid.gif Binary files differindex c718b0e6..c718b0e6 100644..100755 --- a/forum/skins/default/media/images/openid/openid.gif +++ b/forum/skins/default/media/images/openid/openid.gif diff --git a/forum/skins/default/media/images/openid/technorati.ico b/forum/skins/default/media/images/openid/technorati.ico Binary files differindex fa1083c1..fa1083c1 100644..100755 --- a/forum/skins/default/media/images/openid/technorati.ico +++ b/forum/skins/default/media/images/openid/technorati.ico diff --git a/forum/skins/default/media/images/openid/twitter.png b/forum/skins/default/media/images/openid/twitter.png Binary files differnew file mode 100755 index 00000000..9a6552d1 --- /dev/null +++ b/forum/skins/default/media/images/openid/twitter.png diff --git a/forum/skins/default/media/images/openid/verisign.ico b/forum/skins/default/media/images/openid/verisign.ico Binary files differindex 3953af93..3953af93 100644..100755 --- a/forum/skins/default/media/images/openid/verisign.ico +++ b/forum/skins/default/media/images/openid/verisign.ico diff --git a/forum/skins/default/media/images/openid/vidoop.ico b/forum/skins/default/media/images/openid/vidoop.ico Binary files differindex bbd9a0d5..bbd9a0d5 100644..100755 --- a/forum/skins/default/media/images/openid/vidoop.ico +++ b/forum/skins/default/media/images/openid/vidoop.ico diff --git a/forum/skins/default/media/images/openid/wordpress.ico b/forum/skins/default/media/images/openid/wordpress.ico Binary files differindex 31b7d2c2..31b7d2c2 100644..100755 --- a/forum/skins/default/media/images/openid/wordpress.ico +++ b/forum/skins/default/media/images/openid/wordpress.ico diff --git a/forum/skins/default/media/images/openid/yahoo.gif b/forum/skins/default/media/images/openid/yahoo.gif Binary files differindex 42adbfa5..0f0eb8ef 100644..100755 --- a/forum/skins/default/media/images/openid/yahoo.gif +++ b/forum/skins/default/media/images/openid/yahoo.gif diff --git a/forum/skins/default/media/images/quest-bg.gif b/forum/skins/default/media/images/quest-bg.gif Binary files differindex b7540238..b7540238 100644..100755 --- a/forum/skins/default/media/images/quest-bg.gif +++ b/forum/skins/default/media/images/quest-bg.gif diff --git a/forum/skins/default/media/images/vote-accepted-on.png b/forum/skins/default/media/images/vote-accepted-on.png Binary files differindex 2026f3bc..2026f3bc 100644..100755 --- a/forum/skins/default/media/images/vote-accepted-on.png +++ b/forum/skins/default/media/images/vote-accepted-on.png diff --git a/forum/skins/default/media/images/vote-accepted.png b/forum/skins/default/media/images/vote-accepted.png Binary files differindex ecd18551..ecd18551 100644..100755 --- a/forum/skins/default/media/images/vote-accepted.png +++ b/forum/skins/default/media/images/vote-accepted.png diff --git a/forum/skins/default/media/images/vote-arrow-down-on.png b/forum/skins/default/media/images/vote-arrow-down-on.png Binary files differindex 048dbb44..048dbb44 100644..100755 --- a/forum/skins/default/media/images/vote-arrow-down-on.png +++ b/forum/skins/default/media/images/vote-arrow-down-on.png diff --git a/forum/skins/default/media/images/vote-arrow-down.png b/forum/skins/default/media/images/vote-arrow-down.png Binary files differindex e4fdec0a..e4fdec0a 100644..100755 --- a/forum/skins/default/media/images/vote-arrow-down.png +++ b/forum/skins/default/media/images/vote-arrow-down.png diff --git a/forum/skins/default/media/images/vote-arrow-up-on.png b/forum/skins/default/media/images/vote-arrow-up-on.png Binary files differindex 56ad0c25..56ad0c25 100644..100755 --- a/forum/skins/default/media/images/vote-arrow-up-on.png +++ b/forum/skins/default/media/images/vote-arrow-up-on.png diff --git a/forum/skins/default/media/images/vote-arrow-up.png b/forum/skins/default/media/images/vote-arrow-up.png Binary files differindex 6e9a51c7..6e9a51c7 100644..100755 --- a/forum/skins/default/media/images/vote-arrow-up.png +++ b/forum/skins/default/media/images/vote-arrow-up.png diff --git a/forum/skins/default/media/images/vote-favorite-off.png b/forum/skins/default/media/images/vote-favorite-off.png Binary files differindex c1bef074..c1bef074 100644..100755 --- a/forum/skins/default/media/images/vote-favorite-off.png +++ b/forum/skins/default/media/images/vote-favorite-off.png diff --git a/forum/skins/default/media/images/vote-favorite-on.png b/forum/skins/default/media/images/vote-favorite-on.png Binary files differindex 1f9c14ab..1f9c14ab 100644..100755 --- a/forum/skins/default/media/images/vote-favorite-on.png +++ b/forum/skins/default/media/images/vote-favorite-on.png diff --git a/forum/skins/default/media/jquery-openid/images/aol.gif b/forum/skins/default/media/jquery-openid/images/aol.gif Binary files differindex decc4f12..decc4f12 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/aol.gif +++ b/forum/skins/default/media/jquery-openid/images/aol.gif diff --git a/forum/skins/default/media/jquery-openid/images/blogger-1.png b/forum/skins/default/media/jquery-openid/images/blogger-1.png Binary files differindex 8b360ea5..8b360ea5 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/blogger-1.png +++ b/forum/skins/default/media/jquery-openid/images/blogger-1.png diff --git a/forum/skins/default/media/jquery-openid/images/blogger.ico b/forum/skins/default/media/jquery-openid/images/blogger.ico Binary files differindex 1b9730b0..1b9730b0 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/blogger.ico +++ b/forum/skins/default/media/jquery-openid/images/blogger.ico diff --git a/forum/skins/default/media/jquery-openid/images/claimid-0.png b/forum/skins/default/media/jquery-openid/images/claimid-0.png Binary files differindex 4a0ea1b3..4a0ea1b3 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/claimid-0.png +++ b/forum/skins/default/media/jquery-openid/images/claimid-0.png diff --git a/forum/skins/default/media/jquery-openid/images/claimid.ico b/forum/skins/default/media/jquery-openid/images/claimid.ico Binary files differindex 2b80f491..2b80f491 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/claimid.ico +++ b/forum/skins/default/media/jquery-openid/images/claimid.ico diff --git a/forum/skins/default/media/jquery-openid/images/facebook.gif b/forum/skins/default/media/jquery-openid/images/facebook.gif Binary files differindex b997b358..b997b358 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/facebook.gif +++ b/forum/skins/default/media/jquery-openid/images/facebook.gif diff --git a/forum/skins/default/media/jquery-openid/images/flickr.ico b/forum/skins/default/media/jquery-openid/images/flickr.ico Binary files differindex 11f6e07f..11f6e07f 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/flickr.ico +++ b/forum/skins/default/media/jquery-openid/images/flickr.ico diff --git a/forum/skins/default/media/jquery-openid/images/flickr.png b/forum/skins/default/media/jquery-openid/images/flickr.png Binary files differindex 142405a6..142405a6 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/flickr.png +++ b/forum/skins/default/media/jquery-openid/images/flickr.png diff --git a/forum/skins/default/media/jquery-openid/images/google.gif b/forum/skins/default/media/jquery-openid/images/google.gif Binary files differindex 1b6cd07b..1b6cd07b 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/google.gif +++ b/forum/skins/default/media/jquery-openid/images/google.gif diff --git a/forum/skins/default/media/jquery-openid/images/livejournal-1.png b/forum/skins/default/media/jquery-openid/images/livejournal-1.png Binary files differindex e6436081..e6436081 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/livejournal-1.png +++ b/forum/skins/default/media/jquery-openid/images/livejournal-1.png diff --git a/forum/skins/default/media/jquery-openid/images/livejournal.ico b/forum/skins/default/media/jquery-openid/images/livejournal.ico Binary files differindex f3d21ec5..f3d21ec5 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/livejournal.ico +++ b/forum/skins/default/media/jquery-openid/images/livejournal.ico diff --git a/forum/skins/default/media/jquery-openid/images/myopenid-2.png b/forum/skins/default/media/jquery-openid/images/myopenid-2.png Binary files differindex f64fb8e8..f64fb8e8 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/myopenid-2.png +++ b/forum/skins/default/media/jquery-openid/images/myopenid-2.png diff --git a/forum/skins/default/media/jquery-openid/images/myopenid.ico b/forum/skins/default/media/jquery-openid/images/myopenid.ico Binary files differindex ceb06e6a..ceb06e6a 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/myopenid.ico +++ b/forum/skins/default/media/jquery-openid/images/myopenid.ico diff --git a/forum/skins/default/media/jquery-openid/images/openid-inputicon.gif b/forum/skins/default/media/jquery-openid/images/openid-inputicon.gif Binary files differindex cde836c8..cde836c8 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/openid-inputicon.gif +++ b/forum/skins/default/media/jquery-openid/images/openid-inputicon.gif diff --git a/forum/skins/default/media/jquery-openid/images/openid.gif b/forum/skins/default/media/jquery-openid/images/openid.gif Binary files differindex c718b0e6..c718b0e6 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/openid.gif +++ b/forum/skins/default/media/jquery-openid/images/openid.gif diff --git a/forum/skins/default/media/jquery-openid/images/openidico.png b/forum/skins/default/media/jquery-openid/images/openidico.png Binary files differindex ab622669..ab622669 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/openidico.png +++ b/forum/skins/default/media/jquery-openid/images/openidico.png diff --git a/forum/skins/default/media/jquery-openid/images/openidico16.png b/forum/skins/default/media/jquery-openid/images/openidico16.png Binary files differindex ad718ac5..ad718ac5 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/openidico16.png +++ b/forum/skins/default/media/jquery-openid/images/openidico16.png diff --git a/forum/skins/default/media/jquery-openid/images/technorati-1.png b/forum/skins/default/media/jquery-openid/images/technorati-1.png Binary files differindex f7195240..f7195240 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/technorati-1.png +++ b/forum/skins/default/media/jquery-openid/images/technorati-1.png diff --git a/forum/skins/default/media/jquery-openid/images/technorati.ico b/forum/skins/default/media/jquery-openid/images/technorati.ico Binary files differindex fa1083c1..fa1083c1 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/technorati.ico +++ b/forum/skins/default/media/jquery-openid/images/technorati.ico diff --git a/forum/skins/default/media/jquery-openid/images/verisign-2.png b/forum/skins/default/media/jquery-openid/images/verisign-2.png Binary files differindex c1467008..c1467008 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/verisign-2.png +++ b/forum/skins/default/media/jquery-openid/images/verisign-2.png diff --git a/forum/skins/default/media/jquery-openid/images/verisign.ico b/forum/skins/default/media/jquery-openid/images/verisign.ico Binary files differindex 3953af93..3953af93 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/verisign.ico +++ b/forum/skins/default/media/jquery-openid/images/verisign.ico diff --git a/forum/skins/default/media/jquery-openid/images/vidoop.ico b/forum/skins/default/media/jquery-openid/images/vidoop.ico Binary files differindex bbd9a0d5..bbd9a0d5 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/vidoop.ico +++ b/forum/skins/default/media/jquery-openid/images/vidoop.ico diff --git a/forum/skins/default/media/jquery-openid/images/vidoop.png b/forum/skins/default/media/jquery-openid/images/vidoop.png Binary files differindex 032c9e98..032c9e98 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/vidoop.png +++ b/forum/skins/default/media/jquery-openid/images/vidoop.png diff --git a/forum/skins/default/media/jquery-openid/images/wordpress.ico b/forum/skins/default/media/jquery-openid/images/wordpress.ico Binary files differindex 31b7d2c2..31b7d2c2 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/wordpress.ico +++ b/forum/skins/default/media/jquery-openid/images/wordpress.ico diff --git a/forum/skins/default/media/jquery-openid/images/wordpress.png b/forum/skins/default/media/jquery-openid/images/wordpress.png Binary files differindex ee29f0cf..ee29f0cf 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/wordpress.png +++ b/forum/skins/default/media/jquery-openid/images/wordpress.png diff --git a/forum/skins/default/media/jquery-openid/images/yahoo.gif b/forum/skins/default/media/jquery-openid/images/yahoo.gif Binary files differindex 42adbfa5..42adbfa5 100644..100755 --- a/forum/skins/default/media/jquery-openid/images/yahoo.gif +++ b/forum/skins/default/media/jquery-openid/images/yahoo.gif diff --git a/forum/skins/default/media/jquery-openid/jquery.openid.js b/forum/skins/default/media/jquery-openid/jquery.openid.js index 8d1cd204..8d1cd204 100644..100755 --- a/forum/skins/default/media/jquery-openid/jquery.openid.js +++ b/forum/skins/default/media/jquery-openid/jquery.openid.js diff --git a/forum/skins/default/media/jquery-openid/openid.css b/forum/skins/default/media/jquery-openid/openid.css index 1b7aaf82..1b7aaf82 100644..100755 --- a/forum/skins/default/media/jquery-openid/openid.css +++ b/forum/skins/default/media/jquery-openid/openid.css diff --git a/forum/skins/default/media/js/com.cnprog.admin.js b/forum/skins/default/media/js/com.cnprog.admin.js index 39dff48c..39dff48c 100644..100755 --- a/forum/skins/default/media/js/com.cnprog.admin.js +++ b/forum/skins/default/media/js/com.cnprog.admin.js diff --git a/forum/skins/default/media/js/com.cnprog.editor.js b/forum/skins/default/media/js/com.cnprog.editor.js index 18cc5166..18cc5166 100644..100755 --- a/forum/skins/default/media/js/com.cnprog.editor.js +++ b/forum/skins/default/media/js/com.cnprog.editor.js diff --git a/forum/skins/default/media/js/com.cnprog.i18n.js b/forum/skins/default/media/js/com.cnprog.i18n.js index da9bf396..da9bf396 100644..100755 --- a/forum/skins/default/media/js/com.cnprog.i18n.js +++ b/forum/skins/default/media/js/com.cnprog.i18n.js diff --git a/forum/skins/default/media/js/com.cnprog.post.js b/forum/skins/default/media/js/com.cnprog.post.js index 4325e665..4325e665 100644..100755 --- a/forum/skins/default/media/js/com.cnprog.post.js +++ b/forum/skins/default/media/js/com.cnprog.post.js diff --git a/forum/skins/default/media/js/com.cnprog.tag_selector.js b/forum/skins/default/media/js/com.cnprog.tag_selector.js index e3279e65..e3279e65 100644..100755 --- a/forum/skins/default/media/js/com.cnprog.tag_selector.js +++ b/forum/skins/default/media/js/com.cnprog.tag_selector.js diff --git a/forum/skins/default/media/js/com.cnprog.utils.js b/forum/skins/default/media/js/com.cnprog.utils.js index 3f7720c1..3f7720c1 100644..100755 --- a/forum/skins/default/media/js/com.cnprog.utils.js +++ b/forum/skins/default/media/js/com.cnprog.utils.js diff --git a/forum/skins/default/media/js/compress.bat b/forum/skins/default/media/js/compress.bat index 5b2673cf..5b2673cf 100644..100755 --- a/forum/skins/default/media/js/compress.bat +++ b/forum/skins/default/media/js/compress.bat diff --git a/forum/skins/default/media/js/excanvas.pack.js b/forum/skins/default/media/js/excanvas.pack.js index 71d6fbd9..71d6fbd9 100644..100755 --- a/forum/skins/default/media/js/excanvas.pack.js +++ b/forum/skins/default/media/js/excanvas.pack.js diff --git a/forum/skins/default/media/js/flot-build.bat b/forum/skins/default/media/js/flot-build.bat index f9f32cb7..f9f32cb7 100644..100755 --- a/forum/skins/default/media/js/flot-build.bat +++ b/forum/skins/default/media/js/flot-build.bat diff --git a/forum/skins/default/media/js/jquery-1.2.6.js b/forum/skins/default/media/js/jquery-1.2.6.js index 88e661ee..88e661ee 100644..100755 --- a/forum/skins/default/media/js/jquery-1.2.6.js +++ b/forum/skins/default/media/js/jquery-1.2.6.js diff --git a/forum/skins/default/media/js/jquery-1.2.6.min.js b/forum/skins/default/media/js/jquery-1.2.6.min.js index 82b98e1d..82b98e1d 100644..100755 --- a/forum/skins/default/media/js/jquery-1.2.6.min.js +++ b/forum/skins/default/media/js/jquery-1.2.6.min.js diff --git a/forum/skins/default/media/js/jquery.ajaxfileupload.js b/forum/skins/default/media/js/jquery.ajaxfileupload.js index 75292776..75292776 100644..100755 --- a/forum/skins/default/media/js/jquery.ajaxfileupload.js +++ b/forum/skins/default/media/js/jquery.ajaxfileupload.js diff --git a/forum/skins/default/media/js/jquery.flot.js b/forum/skins/default/media/js/jquery.flot.js index 83b61929..83b61929 100644..100755 --- a/forum/skins/default/media/js/jquery.flot.js +++ b/forum/skins/default/media/js/jquery.flot.js diff --git a/forum/skins/default/media/js/jquery.flot.pack.js b/forum/skins/default/media/js/jquery.flot.pack.js index a5714f12..a5714f12 100644..100755 --- a/forum/skins/default/media/js/jquery.flot.pack.js +++ b/forum/skins/default/media/js/jquery.flot.pack.js diff --git a/forum/skins/default/media/js/jquery.form.js b/forum/skins/default/media/js/jquery.form.js index 443114fd..443114fd 100644..100755 --- a/forum/skins/default/media/js/jquery.form.js +++ b/forum/skins/default/media/js/jquery.form.js diff --git a/forum/skins/default/media/js/jquery.i18n.js b/forum/skins/default/media/js/jquery.i18n.js index 0a155a31..0a155a31 100644..100755 --- a/forum/skins/default/media/js/jquery.i18n.js +++ b/forum/skins/default/media/js/jquery.i18n.js diff --git a/forum/skins/default/media/js/jquery.openid.js b/forum/skins/default/media/js/jquery.openid.js index af7d8cb9..af7d8cb9 100644..100755 --- a/forum/skins/default/media/js/jquery.openid.js +++ b/forum/skins/default/media/js/jquery.openid.js diff --git a/forum/skins/default/media/js/jquery.validate.pack.js b/forum/skins/default/media/js/jquery.validate.pack.js index 49134500..49134500 100644..100755 --- a/forum/skins/default/media/js/jquery.validate.pack.js +++ b/forum/skins/default/media/js/jquery.validate.pack.js diff --git a/forum/skins/default/media/js/se_hilite.js b/forum/skins/default/media/js/se_hilite.js index 42e99c8e..42e99c8e 100644..100755 --- a/forum/skins/default/media/js/se_hilite.js +++ b/forum/skins/default/media/js/se_hilite.js diff --git a/forum/skins/default/media/js/se_hilite_src.js b/forum/skins/default/media/js/se_hilite_src.js index b604f156..b604f156 100644..100755 --- a/forum/skins/default/media/js/se_hilite_src.js +++ b/forum/skins/default/media/js/se_hilite_src.js diff --git a/forum/skins/default/media/js/wmd/images/wmd-buttons.png b/forum/skins/default/media/js/wmd/images/wmd-buttons.png Binary files differindex 50b37090..50b37090 100644..100755 --- a/forum/skins/default/media/js/wmd/images/wmd-buttons.png +++ b/forum/skins/default/media/js/wmd/images/wmd-buttons.png diff --git a/forum/skins/default/media/js/wmd/showdown-min.js b/forum/skins/default/media/js/wmd/showdown-min.js index 073613b1..073613b1 100644..100755 --- a/forum/skins/default/media/js/wmd/showdown-min.js +++ b/forum/skins/default/media/js/wmd/showdown-min.js diff --git a/forum/skins/default/media/js/wmd/showdown.js b/forum/skins/default/media/js/wmd/showdown.js index 3f4b9947..3f4b9947 100644..100755 --- a/forum/skins/default/media/js/wmd/showdown.js +++ b/forum/skins/default/media/js/wmd/showdown.js diff --git a/forum/skins/default/media/js/wmd/wmd-min.js b/forum/skins/default/media/js/wmd/wmd-min.js index aa643f1a..aa643f1a 100644..100755 --- a/forum/skins/default/media/js/wmd/wmd-min.js +++ b/forum/skins/default/media/js/wmd/wmd-min.js diff --git a/forum/skins/default/media/js/wmd/wmd-test.html b/forum/skins/default/media/js/wmd/wmd-test.html index d748501a..d748501a 100644..100755 --- a/forum/skins/default/media/js/wmd/wmd-test.html +++ b/forum/skins/default/media/js/wmd/wmd-test.html diff --git a/forum/skins/default/media/js/wmd/wmd.css b/forum/skins/default/media/js/wmd/wmd.css index 80c226c8..80c226c8 100644..100755 --- a/forum/skins/default/media/js/wmd/wmd.css +++ b/forum/skins/default/media/js/wmd/wmd.css diff --git a/forum/skins/default/media/js/wmd/wmd.js b/forum/skins/default/media/js/wmd/wmd.js index 70271d4d..70271d4d 100644..100755 --- a/forum/skins/default/media/js/wmd/wmd.js +++ b/forum/skins/default/media/js/wmd/wmd.js diff --git a/forum/skins/default/media/js/yuicompressor-2.4.2.jar b/forum/skins/default/media/js/yuicompressor-2.4.2.jar Binary files differindex c29470bd..c29470bd 100644..100755 --- a/forum/skins/default/media/js/yuicompressor-2.4.2.jar +++ b/forum/skins/default/media/js/yuicompressor-2.4.2.jar diff --git a/forum/skins/default/media/style/auth.css b/forum/skins/default/media/style/auth.css new file mode 100755 index 00000000..33702758 --- /dev/null +++ b/forum/skins/default/media/style/auth.css @@ -0,0 +1,48 @@ +#bigicon_providers, #smallicon_providers { + display: block; + padding: 0px; + width:600px; + margin:0px 0px 5px 0px; +} + +.provider_logo { + display: inline-block; + padding: 4px; + border: 1px solid #DDD; + text-align: center; + vertical-align: middle; +} + +.provider_logo.big { + height: 40px; + width: 90px; +} + +.provider_logo.small { + height: 32px; + width: 32px; +} + +.provider_logo.selected { + outline: 2px solid #FFF8C6; +} + +.provider_logo .provider_url { + display: none; +} + +.signin_form input[type="text"], .signin_form input[type="password"], .signin_form input[type="submit"] { + height: 28px; + line-height: 22px; + font-size: 140%; + border: 1px solid #999; +} + +.signin_form .icon_input { + padding-left: 20px; +} + +.or_label { + margin-top: 20px; + margin-bottom: 10px; +}
\ No newline at end of file diff --git a/forum/skins/default/media/style/default.css b/forum/skins/default/media/style/default.css index 27da1dab..27da1dab 100644..100755 --- a/forum/skins/default/media/style/default.css +++ b/forum/skins/default/media/style/default.css diff --git a/forum/skins/default/media/style/jquery.autocomplete.css b/forum/skins/default/media/style/jquery.autocomplete.css index 3bf2c2d9..3bf2c2d9 100644..100755 --- a/forum/skins/default/media/style/jquery.autocomplete.css +++ b/forum/skins/default/media/style/jquery.autocomplete.css diff --git a/forum/skins/default/media/style/openid.css b/forum/skins/default/media/style/openid.css index 0d201df2..0d201df2 100644..100755 --- a/forum/skins/default/media/style/openid.css +++ b/forum/skins/default/media/style/openid.css diff --git a/forum/skins/default/media/style/prettify.css b/forum/skins/default/media/style/prettify.css index 10a37577..10a37577 100644..100755 --- a/forum/skins/default/media/style/prettify.css +++ b/forum/skins/default/media/style/prettify.css diff --git a/forum/skins/default/media/style/style.css b/forum/skins/default/media/style/style.css index 02148ab0..4f6b366b 100644..100755 --- a/forum/skins/default/media/style/style.css +++ b/forum/skins/default/media/style/style.css @@ -1,8 +1,5 @@ @import url(jquery.autocomplete.css); -@import url(openid.css); -@import url(prettify.css); -/* 公用 */ body { background: #FFF; font-size: 12px; diff --git a/forum/skins/default/templates/404.html b/forum/skins/default/templates/404.html index 227de3ae..227de3ae 100644..100755 --- a/forum/skins/default/templates/404.html +++ b/forum/skins/default/templates/404.html diff --git a/forum/skins/default/templates/500.html b/forum/skins/default/templates/500.html index 51e73178..51e73178 100644..100755 --- a/forum/skins/default/templates/500.html +++ b/forum/skins/default/templates/500.html diff --git a/forum/skins/default/templates/about.html b/forum/skins/default/templates/about.html index 66dcc3fd..e463ac44 100644..100755 --- a/forum/skins/default/templates/about.html +++ b/forum/skins/default/templates/about.html @@ -26,7 +26,7 @@ These points (very) roughly reflect the level of trust of the community. </p> <p>No points are necessary to ask or answer the questions - so please - - <strong><a href="{% url user_signin %}">join us!</a></strong> + <strong><a href="{% url auth_signin %}">join us!</a></strong> </p> <p> If you would like to find out more about this site - please see <strong><a href="{% url faq %}">frequently asked questions</a></strong>. diff --git a/forum/skins/default/templates/account_settings.html b/forum/skins/default/templates/account_settings.html new file mode 100755 index 00000000..91267d26 --- /dev/null +++ b/forum/skins/default/templates/account_settings.html @@ -0,0 +1,45 @@ +{% extends "base_content.html" %} +<!-- settings.html --> +{% load i18n %} +{% block title %}{% spaceless %}{% trans "Account functions" %}{% endspaceless %}{% endblock %} +{% block head %} +<style type="text/css" media="screen"> + h4 {font-size:12pt;} + dt, dd { padding:0 0 0.35em 0; } + dt { float: left; width: 21ex; } + dd { margin-left: 23ex; } + + #settings-options, #settings-intro { padding: 4em 1.5em;} + #settings-options { min-height: 300px; border-left: 1px solid #333;} + + #settings-options h5 { font-weight: bold;} +</style> +{% endblock %} + +{% block content %} +<div id="main-bar"> + <h3><strong>{{ request.user.username }} {% trans "Profile" %}</strong></h3> +</div> +<div id="settings-options"> + {% if msg %} + <p class="error">{{ msg }}</p> + {% endif %} + + <dl class="list-item"> + <dt>» <a href="{% url user_changepw %}">{% trans "Change password" %}</a></dt> + <dd>{% trans "Give your account a new password." %}</dd> + {% comment %} + <dt>» <a href="{% url user_changeemail %}">{% trans "Change email " %}</a></dt> + <dd>{% trans "Add or update the email address associated with your account." %}</dd> + + <dt>» <a href="{% url user_changeopenid %}">{% trans "Change OpenID" %}</a></dt> + <dd>{% trans "Change openid associated to your account" %}</dd> + + + <dt>» <a href="{% url user_delete %}">{% trans "Delete account" %}</a></dt> + <dd>{% trans "Erase your username and all your data from website" %}</dd> + {% endcomment %} + </dl> +</div> +{% endblock %} +<!-- end settings.html --> diff --git a/forum/skins/default/templates/answer_edit.html b/forum/skins/default/templates/answer_edit.html index 2d736f30..2d736f30 100644..100755 --- a/forum/skins/default/templates/answer_edit.html +++ b/forum/skins/default/templates/answer_edit.html diff --git a/forum/skins/default/templates/answer_edit_tips.html b/forum/skins/default/templates/answer_edit_tips.html index c390da06..c390da06 100644..100755 --- a/forum/skins/default/templates/answer_edit_tips.html +++ b/forum/skins/default/templates/answer_edit_tips.html diff --git a/forum/skins/default/templates/ask.html b/forum/skins/default/templates/ask.html index 083b01d9..083b01d9 100644..100755 --- a/forum/skins/default/templates/ask.html +++ b/forum/skins/default/templates/ask.html diff --git a/forum/skins/default/templates/auth/auth_settings.html b/forum/skins/default/templates/auth/auth_settings.html new file mode 100755 index 00000000..051fb6ba --- /dev/null +++ b/forum/skins/default/templates/auth/auth_settings.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +<!-- changepw.html --> +{% load i18n %} +{% block head %}{% endblock %} +{% block title %}{% spaceless %}{% trans "Authentication settings" %}{% endspaceless %}{% endblock %} +{% block content %} +<div class="headNormal">{% trans "Authentication settings" %}</div> +{% if auth_keys %} + <p class="message">{% blocktrans %}These are the external authentication providers currently associated with your account.{% endblocktrans %}</p> + <div> + {% for key in auth_keys %} + <p>{{ key.name }} (<a href="{% url user_remove_external_provider id=key.id %}">{% trans "remove" %}</a>)</p> + {% endfor %} + </div> +{% endif %} +{% if not auth_keys %} + <p class="message">{% blocktrans %}You currently have no external authentication provider associated with your account.{% endblocktrans %}</p> +{% endif %} +<input type="button" class="submit" value="{% trans "Add new provider" %}" onclick="window.location='{% url user_add_external_provider %}'" /> +{% if has_password %} + <p class="message">{% blocktrans %}This is where you can change your password. Make sure you remember it!{% endblocktrans %}</p> +{% endif %} +{% if not has_password %} + <p class="message">{% blocktrans %}You can set up a password for your account, so you can login using standard username and password!{% endblocktrans %}</p> +{% endif %} +<div class="aligned"> + <form action="" method="post" accept-charset="utf-8"> + <ul id="changepw-form" class="form-horizontal-rows"> + {{form.as_ul}} + </ul> + <div class="submit-row"><input type="submit" class="submit" value="{% if has_password %}{% trans "Change password" %}{% endif %}{% if not has_password %}{% trans "Create password" %}{% endif %}" /></div> + </form> + </div> +{% endblock %} +<!-- end changepw.html --> diff --git a/forum/skins/default/templates/auth/complete.html b/forum/skins/default/templates/auth/complete.html new file mode 100755 index 00000000..cb2dc5aa --- /dev/null +++ b/forum/skins/default/templates/auth/complete.html @@ -0,0 +1,95 @@ +{% extends "base_content.html" %} +<!-- complete.html --> +{% load i18n %} +{% block head %}{% endblock %} +{% block title %}{% spaceless %}{% trans "Connect your OpenID with this site" %}{% endspaceless %}{% endblock %} +{% block content %} + <div id="main-bar" class="headNormal"> + {% trans "Connect your OpenID with your account on this site" %} + </div> + <div id="completetxt" > + <div class="message"> + <b>{% trans "You are here for the first time with " %}{{ provider }}</b> + {% trans "Please create your screen name and save your email address. Saved email address will let you subscribe for the updates on the most interesting questions and will be used to create and retrieve your unique avatar image. " %} + </div> + <p style="display:none">{% trans "This account already exists, please use another." %}</p> + </div> + + {% if form1.errors %} + <ul class="errorlist"> + {% if form1.non_field_errors %} + {% for error in form1.non_field_errors %} + <li>{{error}}</li> + {% endfor %} + {% endif %} + </ul> + {% endif %} + {% comment %} + {% if form2.errors %}<!--form2 is dysfunctional so commented out --> + <div class="errors"> + <span class="big">{% trans "Sorry, looks like we have some errors:" %}</span><br/> + <ul class="error-list"> + {% if form2.username.errors %} + <li><span class="error">{{ form2.username.errors|join:", " }}</span></li> + {% endif %} + {% if form2.password.errors %} + <li><span class="error">{{ form2.password.errors|join:", " }}</span></li> + {% endif %} + </ul> + </div> + {% endif %} + {% endcomment %} + + <div class="login"> + <form name="fregister" action="" method="POST"> + {{ form1.next }} + <div class="form-row-vertical"> + <label for="id_username">{% trans "Screen name label" %}</label> + {% if form1.username.errors %} + <p class="error">{{ form1.username.errors|join:", " }}</p> + {% endif %} + {{ form1.username }} + </div> + <div class="form-row-vertical margin-bottom"> + <label for="id_email">{% trans "Email address label" %}</label> + {% if form1.email.errors %} + <p class="error">{{ form1.email.errors|join:", " }}</p> + {% endif %} + {{ form1.email }} + </div> + <p>{% trans "receive updates motivational blurb" %}</p> + <div class='simple-subscribe-options'> + {{email_feeds_form.subscribe}} + {% if email_feeds_form.errors %} + <p class="error">{% trans "please select one of the options above" %}</p> + {% endif %} + </div> + <p class='space-above'>{% trans "Tag filter tool will be your right panel, once you log in." %}</p> + <div class="submit-row"><input type="submit" class="submit" name="bnewaccount" value="{% trans "create account" %}"/></div> + </form> + </div> + {% comment %}<!-- this form associates openID with an existing password-protected account, not yet functional --> + {% if form2 %} + <div class="login" style="display:none"> + <form name="fverify" action="{% url user_register %}" method="POST"> + {{ form2.next }} + <fieldset style="padding:10px"> + <legend class="big">{% trans "Existing account" %}</legend> + <div class="form-row"><label for="id_username">{% trans "user name" %}</label><br/>{{ form2.username }}</div> + <div class="form-row"><label for="id_passwordl">{% trans "password" %}</label><br/>{{ form2.password }}</div> + <p><span class='big strong'>(Optional) receive updates by email</span> - only sent when there are any.</p> + <div class='simple-subscribe-options'> + {{email_feeds_form.subscribe}} + </div> + <!--todo double check translation from chinese 确认 = "Register" --> + <div class="submit-row"> + <input type="submit" class="submit" name="bverify" value="{% trans "Register" %}"/> + <a href="{% url user_sendpw %}">{% trans "Forgot your password?" %}</a> + </div> + </fieldset> + </form> + </div> + {% endif %} + {% endcomment %} +{% endblock %} +<!-- end complete.html --> diff --git a/forum/skins/default/templates/auth/email_validation.html b/forum/skins/default/templates/auth/email_validation.html new file mode 100755 index 00000000..a4126a69 --- /dev/null +++ b/forum/skins/default/templates/auth/email_validation.html @@ -0,0 +1,20 @@ +{% extends "email_base.html" %}
+{% load i18n %}
+{% load extra_tags %}
+
+{% block content %}
+ <p>{% trans "Greetings from the Q&A forum" %},</p>
+
+ <p>{% trans "To make use of the Forum, please follow the link below:" %}</p>
+
+ <a href="{% fullurl auth_validate_email user=user.id,code=validation_code %}">{% fullurl auth_validate_email user=user.id,code=validation_code %}</a>
+
+ <p>{% trans "Following the link above will help us verify your email address." %}</p>
+
+ <p>{% blocktrans %}If you beleive that this message was sent in mistake -
+ no further action is needed. Just ingore this email, we apologize
+ for any inconvenience{% endblocktrans %}</p>
+
+ <p>{% blocktrans %}Sincerely,<br />
+ Forum Administrator{% endblocktrans %}</p>
+{% endblock %}
diff --git a/forum/skins/default/templates/auth/signin.html b/forum/skins/default/templates/auth/signin.html new file mode 100755 index 00000000..78e6c76d --- /dev/null +++ b/forum/skins/default/templates/auth/signin.html @@ -0,0 +1,165 @@ +{% extends "base.html" %}
+
+{% load i18n %}
+{% load extra_tags %}
+
+{% block forejs %}
+ <link rel="stylesheet" type="text/css" media="screen" href="{% media "/media/style/auth.css" %}"/>
+ {% for provider in all_providers %}
+ {% for location in provider.extra_css %}
+ <link rel="stylesheet" type="text/css" media="screen" href="{{ location }}"/>
+ {% endfor %}
+ {% endfor %}
+{% endblock %}
+
+{% block content %}
+ {% for provider in all_providers %}
+ {% if provider.pre_code %}
+ {{ provider.pre_code|safe }}
+ {% endif %}
+ {% endfor %}
+ <div class="headNormal">
+ {% trans "User login" %}
+ </div>
+ {% if msg %}
+ <p class="warning">{{ msg }}</p>
+ {% endif %}
+
+ <div style="width:600px;float:left;margin-bottom:5px;">
+ {% trans "Click to sign in through any of these services." %}
+ </div>
+ {% if request.user.is_anonymous %}
+ <div style="width:600px;float:left;margin-bottom:5px;">
+ <input type="checkbox" checked="checked" id="validate_email" />
+ {% trans "Take the oppurtunity to validate my email next to the external provider I choose." %}
+ </div>
+ {% endif %}
+ <div id="bigicon_providers">
+ {% for provider in bigicon_providers %}
+ <div class="provider_logo big" name="{{ provider.id }}">
+ {% ifequal provider.type "DIRECT" %}
+ <a class="provider_direct" href="{% url auth_provider_signin provider=provider.id %}">
+ <img src="{% media provider.icon %}" />
+ </a>
+ {% endifequal %}
+ {% ifequal provider.type "CUSTOM" %}
+ {% include provider.code_template %}
+ {% endifequal %}
+ {% ifequal provider.type "SIMPLE_FORM" %}
+ <img alt="{{ provider.simple_form_context.your_what }}" class="simple_form_provider" src="{% media provider.icon %}" />
+ {% endifequal %}
+ </div>
+ {% endfor %}
+ </div>
+ <div id="smallicon_providers">
+ {% for provider in smallicon_providers %}
+ <div class="provider_logo small" name="{{ provider.id }}">
+ {% ifequal provider.type "DIRECT" %}
+ <a class="provider_direct" href="{% url auth_provider_signin provider=provider.id %}">
+ <img src="{% media provider.icon %}" />
+ </a>
+ {% endifequal %}
+ {% ifequal provider.type "CUSTOM" %}
+ {% include provider.code_template %}
+ {% endifequal %}
+ {% ifequal provider.type "SIMPLE_FORM" %}
+ <img alt="{{ provider.simple_form_context.your_what }}" class="simple_form_provider" src="{% media provider.icon %}" />
+ {% endifequal %}
+ </div>
+ {% endfor %}
+ </div>
+ <form name="signin_form" id="signin_form" class="signin_form" method="POST" action="">
+ <div id="signin_form_slot"></div>
+ <input type="hidden" class="validate_email" name="validate_email" value="yes" />
+ </form>
+ {% for provider in stackitem_providers %}
+ <h3 class="or_label">{% trans 'Or...' %}</h3>
+ <form class="signin_form" method="POST" action="{% url auth_provider_signin provider=provider.id %}">
+ {% include provider.stack_item_template %}
+ <input type="hidden" class="validate_email" name="validate_email" value="yes" />
+ </form>
+ {% endfor %}
+ <h3 class="or_label">{% trans 'Or...' %}</h3>
+ <fieldset>
+ {% trans 'Click' %} <a href="{% url auth_request_tempsignin %}">here</a> {% trans "if you're having troubles signing in." %}
+ </fieldset>
+ <script type="text/html" id="simple_form_template">
+ <fieldset id="slot_form">
+ <p id="provider_name_slot">{% trans 'Enter your ' %}%%YOUR_WHAT%%</p>
+ <div><p><span></span>
+ <input id="input_field" type="text" name="input_field" /><span></span>
+ <input id="ssignin" name="ssignin" type="submit" value="Login" />
+ </p></div>
+ <input type="hidden" class="validate_email" name="validate_email" value="yes" />
+ </fieldset>
+ </script>
+ <script type="text/javascript">
+ $(function() {
+ var signin_url = "{% url auth_provider_signin provider='PROVIDER' %}";
+
+ function set_validate_email() {
+ var validate = $('#validate_email').attr('checked') ? 'yes' : 'no';
+ $('.validate_email').attr('value', validate);
+
+ $('.provider_direct').each(function() {
+ var current_url = $(this).attr('href');
+ if (!/\?validate_email\=(yes|no)$/.test(current_url)) {
+ current_url += ('?validate_email=' + validate);
+ } else {
+ current_url = current_url.replace(/(yes|no)$/, validate);
+ }
+
+ $(this).attr('href', current_url);
+ })
+ }
+
+ $('#validate_email').change(set_validate_email);
+
+ function set_form_action(el) {
+ var provider = el.parents('.provider_logo').attr('name');
+ $('#signin_form').attr('action', signin_url.replace('PROVIDER', provider));
+ }
+
+ $('.provider_logo').click(function() {
+ $('.provider_logo').removeClass('selected');
+ $(this).addClass('selected');
+ });
+
+ $('.simple_form_provider').click(function() {
+ $('#signin_form_slot').html('');
+ var new_html = $('#simple_form_template').html()
+ .replace('%%YOUR_WHAT%%', $(this).attr('alt'));
+ $('#signin_form_slot').html(new_html);
+ set_form_action($(this));
+ set_validate_email();
+ })
+
+ set_validate_email();
+ });
+ </script>
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ <h3 class="subtitle">{% trans "Why use OpenID?" %}</h3>
+ <ul class="list-item">
+ <li>
+ {% trans "with openid it is easier" %}
+ </li>
+ <li>
+ {% trans "reuse openid" %}
+ </li>
+ <li>
+ {% trans "openid is widely adopted" %}
+ </li>
+ <li>
+ {% trans "openid is supported open standard" %}
+ </li>
+
+ </ul>
+ <p class="info-box-follow-up-links">
+ <a href="http://openid.net/what/" target="_blank">{% trans "Find out more" %} </a><br/>
+ <a href="http://openid.net/get/" target="_blank">{% trans "Get OpenID" %} </a>
+ </p>
+</div>
+{% endblock%}
\ No newline at end of file diff --git a/forum/skins/default/templates/auth/signup.html b/forum/skins/default/templates/auth/signup.html new file mode 100755 index 00000000..bd5750b4 --- /dev/null +++ b/forum/skins/default/templates/auth/signup.html @@ -0,0 +1,32 @@ +{% extends "base_content.html" %} +<!--signup.html--> +{% load i18n %} +{% block title %}{% spaceless %}{% trans "Signup" %}{% endspaceless %}{% endblock %} + +{% block content %} +<div class="headNormal"> + {% trans "Create login name and password" %} +</div> +<p class="message">{% trans "Traditional signup info" %}</p> +<form action="" method="post" accept-charset="utf-8"> + <ul class="form-horizontal-rows"> + <li><label for="usename_id">{{form.username.label}}</label>{{form.username}}{{form.username.errors}}</li> + <li><label for="email_id">{{form.email.label}}</label>{{form.email}}{{form.email.errors}}</li> + <li><label for="password1_id">{{form.password1.label}}</label>{{form.password1}}{{form.password1.errors}}</li> + <li><label for="password2_id">{{form.password2.label}}</label>{{form.password2}}{{form.password2.errors}}</li> + </ul> + <p class="margin-top">{% trans "receive updates motivational blurb" %}</p> + <div class='simple-subscribe-options'> + {{ email_feeds_form.subscribe }} + {% if email_feeds_form.errors %} + <p class="error">{% trans "please select one of the options above" %}</p> + {% endif %} + </div> + {% comment %}<p class="signup_p">{% trans "Please read and type in the two words below to help us prevent automated account creation." %}</p> + {{form.recaptcha}}{% endcomment %} + <div class="submit-row"><input type="submit" class="submit" value="{% trans "Create Account" %}" /> + <strong>{% trans "or" %} + <a href="{% url auth_signin %}">{% trans "return to login page" %}</a></strong></div> +</form> +{% endblock %} +<!--end signup.html--> diff --git a/forum/skins/default/templates/auth/temp_login_email.html b/forum/skins/default/templates/auth/temp_login_email.html new file mode 100755 index 00000000..063608fe --- /dev/null +++ b/forum/skins/default/templates/auth/temp_login_email.html @@ -0,0 +1,20 @@ +{% extends "email_base.html" %}
+{% load i18n %}
+{% load extra_tags %}
+
+{% block content %}
+ <p>{% trans "Greetings from the Q&A forum" %},</p>
+
+ <p>{% trans "You're seeing this because someone requested a temporary login link" %}</p>
+
+ <a href="{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}">{% fullurl auth_tempsignin user=user.id,code=temp_login_code %}</a>
+
+ <p>{% trans "Following the link above will give you access to your account." %}</p>
+
+ <p>{% blocktrans %}If you beleive that this message was sent in mistake -
+ no further action is needed. Just ingore this email, we apologize
+ for any inconvenience{% endblocktrans %}</p>
+
+ <p>{% blocktrans %}Sincerely,<br />
+ Forum Administrator{% endblocktrans %}</p>
+{% endblock %}
diff --git a/forum/skins/default/templates/auth/temp_login_request.html b/forum/skins/default/templates/auth/temp_login_request.html new file mode 100755 index 00000000..772f18fb --- /dev/null +++ b/forum/skins/default/templates/auth/temp_login_request.html @@ -0,0 +1,28 @@ +{% extends "base.html" %}
+
+{% load i18n %}
+{% block head %}{% endblock %}
+{% block title %}{% spaceless %}{% trans "Request temporary login key" %}{% endspaceless %}{% endblock %}
+{% block content %}
+<div class="headNormal">{% trans "Account: request temporary login key" %}</div>
+<p class="message">{% blocktrans %}
+ If you're experiencing problems accessing your account, or if you forgot your password,
+ here you can request a temporary login key. Fill out your account email and we'll send you a temporary access link that
+ will enable you to access your account. This token is valid only once and for a limited period of time.
+ {% endblocktrans %}</p>
+<div class="aligned">
+ {% if form.errors %}
+ <ul class="errorlist">
+ {% for error in form.errors %}
+ <li>{{ error }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ <form action="" method="post" accept-charset="utf-8">
+ <ul id="changepw-form" class="form-horizontal-rows">
+ {{form.as_ul}}
+ </ul>
+ <div class="submit-row"><input type="submit" class="submit" value="{% trans "Send link" %}" /></div>
+ </form>
+ </div>
+{% endblock %}
\ No newline at end of file diff --git a/forum/skins/default/templates/badge.html b/forum/skins/default/templates/badge.html index af6aa2a2..af6aa2a2 100644..100755 --- a/forum/skins/default/templates/badge.html +++ b/forum/skins/default/templates/badge.html diff --git a/forum/skins/default/templates/badges.html b/forum/skins/default/templates/badges.html index 8de93df5..8de93df5 100644..100755 --- a/forum/skins/default/templates/badges.html +++ b/forum/skins/default/templates/badges.html diff --git a/forum/skins/default/templates/base.html b/forum/skins/default/templates/base.html index 4851f079..cd7aba23 100755 --- a/forum/skins/default/templates/base.html +++ b/forum/skins/default/templates/base.html @@ -3,7 +3,7 @@ {% load extra_filters %} {% load extra_tags %} {% load i18n %} -<html xmlns="http://www.w3.org/1999/xhtml"{% if fb_api_key %} xmlns:fb="http://www.facebook.com/2008/fbml"{% endif %}> +<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title> {% spaceless %} diff --git a/forum/skins/default/templates/base_content.html b/forum/skins/default/templates/base_content.html index d1cf673b..d1cf673b 100644..100755 --- a/forum/skins/default/templates/base_content.html +++ b/forum/skins/default/templates/base_content.html diff --git a/forum/skins/default/templates/book.html b/forum/skins/default/templates/book.html index 8574fa73..8574fa73 100644..100755 --- a/forum/skins/default/templates/book.html +++ b/forum/skins/default/templates/book.html diff --git a/forum/skins/default/templates/close.html b/forum/skins/default/templates/close.html index d9e73507..d9e73507 100644..100755 --- a/forum/skins/default/templates/close.html +++ b/forum/skins/default/templates/close.html diff --git a/forum/skins/default/templates/edit_user_email_feeds_form.html b/forum/skins/default/templates/edit_user_email_feeds_form.html index 65902e7e..65902e7e 100644..100755 --- a/forum/skins/default/templates/edit_user_email_feeds_form.html +++ b/forum/skins/default/templates/edit_user_email_feeds_form.html diff --git a/forum/skins/default/templates/email_base.html b/forum/skins/default/templates/email_base.html new file mode 100755 index 00000000..b74741e3 --- /dev/null +++ b/forum/skins/default/templates/email_base.html @@ -0,0 +1,26 @@ +{% load extra_filters %}
+{% load extra_tags %}
+{% load i18n %}
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link href="{% fullmedia "/media/style/style.css" %}" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+ <a href="{% fullurl index %}">
+ <img src="{% fullmedia "/media/images/logo.png" %}" title="{% trans "home" %}" alt="{{settings.APP_TITLE}} logo"/>
+ </a>
+ <br />
+ <p>{{ settings.APP_TITLE }}</p>
+ <br /><br />
+ <div id="wrapper">
+ <div id="room">
+ <div id="CALeft">
+ {% block content%}
+ {% endblock%}
+ </div>
+ </div>
+ <div class="spacer3"></div>
+ </div>
+ </body>
+</html>
\ No newline at end of file diff --git a/forum/skins/default/templates/faq.html b/forum/skins/default/templates/faq.html index 236f4f76..284e1229 100644..100755 --- a/forum/skins/default/templates/faq.html +++ b/forum/skins/default/templates/faq.html @@ -97,6 +97,7 @@ </table> </div> + {% comment %} {% ifequal settings.EMAIL_VALIDATION 'on' %} <div> <a id='validate'></a><h3 class="subtitle">{% trans "how to validate email title" %}</h3> @@ -104,6 +105,7 @@ {% blocktrans %}how to validate email info with {{send_email_key_url}} {{gravatar_faq_url}}{% endblocktrans %} </div> {% endifequal %} + {% endcomment %} <div> <a id='gravatar'></a><h3 class="subtitle">{% trans "what is gravatar" %}</h3> <p>{% trans "gravatar faq info" %}</p> @@ -111,7 +113,7 @@ <div> <h3 class="subtitle">{% trans "To register, do I need to create new password?" %}</h3> <p>{% trans "No, you don't have to. You can login through any service that supports OpenID, e.g. Google, Yahoo, AOL, etc." %} - <strong><a href="{% url user_signin %}">{% trans "Login now!" %}</a> »</strong> + <strong><a href="{% url auth_signin %}">{% trans "Login now!" %}</a> »</strong> </p> </div> diff --git a/forum/skins/default/templates/feedback.html b/forum/skins/default/templates/feedback.html index 38bb48ff..38bb48ff 100644..100755 --- a/forum/skins/default/templates/feedback.html +++ b/forum/skins/default/templates/feedback.html diff --git a/forum/skins/default/templates/feedback_email.txt b/forum/skins/default/templates/feedback_email.txt index df768180..df768180 100644..100755 --- a/forum/skins/default/templates/feedback_email.txt +++ b/forum/skins/default/templates/feedback_email.txt diff --git a/forum/skins/default/templates/feeds/rss_description.html b/forum/skins/default/templates/feeds/rss_description.html index fa781907..fa781907 100644..100755 --- a/forum/skins/default/templates/feeds/rss_description.html +++ b/forum/skins/default/templates/feeds/rss_description.html diff --git a/forum/skins/default/templates/feeds/rss_title.html b/forum/skins/default/templates/feeds/rss_title.html index 7899fce3..7899fce3 100644..100755 --- a/forum/skins/default/templates/feeds/rss_title.html +++ b/forum/skins/default/templates/feeds/rss_title.html diff --git a/forum/skins/default/templates/footer.html b/forum/skins/default/templates/footer.html index 89d4801f..66ea2bc5 100644..100755 --- a/forum/skins/default/templates/footer.html +++ b/forum/skins/default/templates/footer.html @@ -21,8 +21,8 @@ {% endspaceless %} </div> <p> - <a href="http://github.com/cnprog/CNPROG/network" target="_blank"> - powered by cnprog platform + <a href="http://osqa.net" target="_blank"> + powered by OSQA </a> </p> </div> diff --git a/forum/skins/default/templates/header.html b/forum/skins/default/templates/header.html index 3afc46c5..e65ac73a 100644..100755 --- a/forum/skins/default/templates/header.html +++ b/forum/skins/default/templates/header.html @@ -5,10 +5,10 @@ <div id="navBar"> <div id="top"> {% if request.user.is_authenticated %} - <a href="{% url users %}{{ request.user.id }}/{{ request.user.username }}/">{{ request.user.username }}</a> {% get_score_badge request.user %} + <a href="{% url user_profile id=request.user.id,slug=request.user.username|slugify %}">{{ request.user.username }}</a> {% get_score_badge request.user %} <a href="{% url logout %}">{% trans "logout" %}</a> {% else %} - <a href="{% url user_signin %}">{% trans "login" %}</a> + <a href="{% url auth_signin %}">{% trans "login" %}</a> {% endif %} <a href="{% url about %}">{% trans "about" %}</a> <a href="{% url faq %}">{% trans "faq" %}</a> diff --git a/forum/skins/default/templates/index.html b/forum/skins/default/templates/index.html index 5bbb192b..61a5e6b3 100755 --- a/forum/skins/default/templates/index.html +++ b/forum/skins/default/templates/index.html @@ -5,6 +5,7 @@ {% load humanize %}
{% load extra_filters %}
{% load smart_if %}
+{% load cache %}
{% block title %}{% spaceless %}{% trans "Home" %}{% endspaceless %}{% endblock %}
{% block meta %}<meta name="keywords" content="{{ settings.APP_KEYWORDS }}" />
<meta name="description" content="{{ settings.APP_DESCRIPTION }}" />{% endblock %}
@@ -31,41 +32,7 @@ </div>
</div>
<!-- ???? -->
-<div id="listA">
- {% for question in questions.object_list %}
- <div class="short-summary">
- <div class="counts">
- <div class="votes">
- <div class="item-count">{{question.score|intcomma}}</div>
- <div>{% trans "votes" %}</div>
- </div >
- <div {% if question.answer_accepted %}title="{% trans "this answer has been accepted to be correct" %}"{% endif %} class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
- <div class="item-count">{{question.answer_count|intcomma}}</div>
- <div>{% trans "answers" %}</div>
- </div>
- <div class="views">
- <div class="item-count">{{question.view_count|cnprog_intword|safe}}</div>
- <div>{% trans "views" %}</div>
- </div>
- </div>
-
- <h2><a title="{{question.summary}}" href="{% url question id=question.id %}{{question.title|slugify}}">{{question.title}}</a></h2>
-
- <div class="userinfo">
- <span class="relativetime" title="{{question.last_activity_at}}">{% diff_date question.last_activity_at %}</span>
- {% if question.last_activity_by %}
- <a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a> {% get_score_badge question.last_activity_by %}
- {% endif %}
- </div>
-
- <div class="tags">
- {% for tag in question.tagname_list %}
- <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %} '{{ tag }}' {% trans "using tags" %}" rel="tag">{{ tag }}</a>
- {% endfor %}
- </div>
- </div>
- {% endfor %}
-</div>
+{% include "question_list.html" %}
{% endblock %}
{% block sidebar %}
@@ -85,10 +52,12 @@ <h3>{% trans "Recent tags" %}</h3>
<div class="body">
<div class="tags">
+ {% cache 60 recent_tags %}
{% for tag in tags %}
<a rel="tag"
title="{% blocktrans with tag.name as tagname %}see questions tagged '{{tagname}}'{% endblocktrans %}" href="{% url tag_questions tag.name|urlencode %}">{{ tag.name }}</a>
{% endfor %}
+ {% endcache %}
</div>
<div class="more"><a href="{% url tags %}">{% trans "popular tags" %} </a> </div>
</div>
@@ -98,20 +67,22 @@ <h3>{% trans "Recent awards" %}</h3>
<div class="body">
<ul class="badge-list">
+ {% cache 60 recent_awards %}
{% for award in awards %}
<li>
<a href="{% url badges %}{{award.badge_id}}/{{award.badge_name}}" title="{{ award.badge_description }}" class="medal">
- <span class="badge{{ award.badge_type }}">●</span> {{ award.badge_name }}</a> {% trans "given to" %}
- <a href="{% url users %}{{award.user_id}}/{{award.user_name}}">{{ award.user_name }}</a>
+ <span class="badge{{ award.badge_type }}">●</span> {{ award.badge_name }}</a>
+ <a href="/users/{{ award.user_id }}/{{ award.user_name|slugify }}/">{{ award.user_name }}</a>
</li>
{% endfor %}
+ {% endcache %}
</ul>
<div class="more"><a href="{% url badges %}">{% trans "all awards" %} </a> </div>
</div>
</div>
{% endif %}
<div id="feeds">
-<a href="{% media "/feeds/rss" %}" title="{% trans "subscribe to last 30 questions by RSS" %}">{% trans "subscribe to the questions feed" %}</a>
+<a href="{% url latest_questions_feed %}" title="{% trans "subscribe to last 30 questions by RSS" %}">{% trans "subscribe to the questions feed" %}</a>
</div>
{% endblock %}
{% block tail %}
diff --git a/forum/skins/default/templates/logout.html b/forum/skins/default/templates/logout.html index 650ba044..650ba044 100644..100755 --- a/forum/skins/default/templates/logout.html +++ b/forum/skins/default/templates/logout.html diff --git a/forum/skins/default/templates/notarobot.html b/forum/skins/default/templates/notarobot.html index 698c5696..698c5696 100644..100755 --- a/forum/skins/default/templates/notarobot.html +++ b/forum/skins/default/templates/notarobot.html diff --git a/forum/skins/default/templates/pagesize.html b/forum/skins/default/templates/pagesize.html index 5037f1f6..5037f1f6 100644..100755 --- a/forum/skins/default/templates/pagesize.html +++ b/forum/skins/default/templates/pagesize.html diff --git a/forum/skins/default/templates/paginator.html b/forum/skins/default/templates/paginator.html index 2fba5425..2fba5425 100644..100755 --- a/forum/skins/default/templates/paginator.html +++ b/forum/skins/default/templates/paginator.html diff --git a/forum/skins/default/templates/post_contributor_info.html b/forum/skins/default/templates/post_contributor_info.html index 9997be5f..9997be5f 100644..100755 --- a/forum/skins/default/templates/post_contributor_info.html +++ b/forum/skins/default/templates/post_contributor_info.html diff --git a/forum/skins/default/templates/privacy.html b/forum/skins/default/templates/privacy.html index e66086dd..e66086dd 100644..100755 --- a/forum/skins/default/templates/privacy.html +++ b/forum/skins/default/templates/privacy.html diff --git a/forum/skins/default/templates/question.html b/forum/skins/default/templates/question.html index abf7cae2..4fc37dbd 100644..100755 --- a/forum/skins/default/templates/question.html +++ b/forum/skins/default/templates/question.html @@ -5,6 +5,7 @@ {% load smart_if %}
{% load humanize %}
{% load i18n %}
+{% load cache %}
{% block title %}{% spaceless %}{{ question.get_question_title }}{% endspaceless %}{% endblock %}
{% block forejs %}
<meta name="description" content="{{question.summary}}" />
@@ -32,7 +33,7 @@ {% if not question.closed and request.user.is_authenticated %}initEditor();{% endif %}
lanai.highlightSyntax();
- $('#btLogin').bind('click', function(){window.location.href='{% url user_signin %}'; } )
+ $('#btLogin').bind('click', function(){window.location.href='{% url auth_signin %}'; } )
});
function initEditor(){
@@ -164,8 +165,8 @@ {% endjoinitems %}
</div>
<div class="post-update-info-container">
- {% post_contributor_info question "original_author" %}
- {% post_contributor_info question "last_updater" %}
+ {% post_contributor_info question "original_author" %}
+ {% post_contributor_info question "last_updater" %}
</div>
<div class="comments-container" id="comments-container-question-{{question.id}}">
{% for comment in question.get_comments|slice:":5" %}
@@ -313,8 +314,8 @@ {% endjoinitems %}
</div>
<div class="post-update-info-container">
- {% post_contributor_info answer "original_author" %}
- {% post_contributor_info answer "last_updater" %}
+ {% post_contributor_info answer "original_author" %}
+ {% post_contributor_info answer "last_updater" %}
</div>
<div class="comments-container" id="comments-container-answer-{{answer.id}}">
{% for comment in answer.get_comments|slice:":5" %}
@@ -466,6 +467,7 @@ {% endblock %}
{% block sidebar %}
+{% cache 600 questions_tags question.id %}
<div class="boxC">
<p>
{% trans "Question tags" %}:
@@ -487,17 +489,21 @@ {% trans "last updated" %}: <strong title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</strong>
</p>
</div>
-
+{% endcache %}
+{% cache 1800 related_questions question.id %}
<div class="boxC">
<h3 class="subtitle">{% trans "Related questions" %}</h3>
<div class="questions-related">
- {% for question in similar_questions %}
+
+ {% for question in similar_questions.data %}
<p>
<a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
</p>
{% endfor %}
+
</div>
</div>
+{% endcache %}
{% endblock %}
diff --git a/forum/skins/default/templates/question_edit.html b/forum/skins/default/templates/question_edit.html index fe711849..fe711849 100644..100755 --- a/forum/skins/default/templates/question_edit.html +++ b/forum/skins/default/templates/question_edit.html diff --git a/forum/skins/default/templates/question_edit_tips.html b/forum/skins/default/templates/question_edit_tips.html index 4cabea79..4cabea79 100644..100755 --- a/forum/skins/default/templates/question_edit_tips.html +++ b/forum/skins/default/templates/question_edit_tips.html diff --git a/forum/skins/default/templates/question_list.html b/forum/skins/default/templates/question_list.html new file mode 100755 index 00000000..cf69d133 --- /dev/null +++ b/forum/skins/default/templates/question_list.html @@ -0,0 +1,42 @@ +{% load cache %}
+{% load i18n %}
+{% load humanize %}
+{% load extra_filters %}
+{% load extra_tags %}
+
+<div id="listA">
+ {% for question in questions.object_list %}
+ {% cache 60 question_in_list question.id %}
+ <div class="short-summary">
+ <div class="counts">
+ <div class="votes">
+ <div class="item-count">{{question.score|intcomma}}</div>
+ <div>{% trans "votes" %}</div>
+ </div >
+ <div {% if question.answer_accepted %}title="{% trans "this answer has been accepted to be correct" %}"{% endif %} class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
+ <div class="item-count">{{question.answer_count|intcomma}}</div>
+ <div>{% trans "answers" %}</div>
+ </div>
+ <div class="views">
+ <div class="item-count">{{question.view_count|cnprog_intword|safe}}</div>
+ <div>{% trans "views" %}</div>
+ </div>
+ </div>
+
+ <h2><a title="{{question.summary}}" href="{% url question id=question.id %}{{question.title|slugify}}">{{question.title}}</a></h2>
+ <div class="userinfo">
+ <span class="relativetime" title="{{question.last_activity_at}}">{% diff_date question.last_activity_at %}</span>
+ {% if question.last_activity_by %}
+ <a href="/users/{{ question.last_activity_by.id }}/{{ question.last_activity_by.username|slugify }}/">{{ question.last_activity_by }}</a> {% get_score_badge question.last_activity_by %}
+ {% endif %}
+ </div>
+
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %} '{{ tag }}' {% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endcache %}
+ {% endfor %}
+</div>
\ No newline at end of file diff --git a/forum/skins/default/templates/question_retag.html b/forum/skins/default/templates/question_retag.html index 03f3da04..03f3da04 100644..100755 --- a/forum/skins/default/templates/question_retag.html +++ b/forum/skins/default/templates/question_retag.html diff --git a/forum/skins/default/templates/question_summary_list_roll.html b/forum/skins/default/templates/question_summary_list_roll.html index 57685d6d..57685d6d 100644..100755 --- a/forum/skins/default/templates/question_summary_list_roll.html +++ b/forum/skins/default/templates/question_summary_list_roll.html diff --git a/forum/skins/default/templates/reopen.html b/forum/skins/default/templates/reopen.html index 37fb69c1..37fb69c1 100644..100755 --- a/forum/skins/default/templates/reopen.html +++ b/forum/skins/default/templates/reopen.html diff --git a/forum/skins/default/templates/revisions_answer.html b/forum/skins/default/templates/revisions_answer.html index b2e33dfe..b2e33dfe 100644..100755 --- a/forum/skins/default/templates/revisions_answer.html +++ b/forum/skins/default/templates/revisions_answer.html diff --git a/forum/skins/default/templates/revisions_question.html b/forum/skins/default/templates/revisions_question.html index 86d52a36..86d52a36 100644..100755 --- a/forum/skins/default/templates/revisions_question.html +++ b/forum/skins/default/templates/revisions_question.html diff --git a/forum/skins/default/templates/tag_selector.html b/forum/skins/default/templates/tag_selector.html index 7686d717..5e3e2ad8 100644..100755 --- a/forum/skins/default/templates/tag_selector.html +++ b/forum/skins/default/templates/tag_selector.html @@ -37,6 +37,6 @@ <input id="ignoredTagAdd" type="submit" value="{% trans "Add" %}"/> <p id="hideIgnoredTagsControl"> <input id="hideIgnoredTagsCb" type="checkbox" {% if request.user.hide_ignored_questions %}checked="checked"{% endif %} /> - <label id="hideIgnoredTagsLabel" for="hideIgnoredTagsCb">{% trans "keep ingored questions hidden" %}</label> + <label id="hideIgnoredTagsLabel" for="hideIgnoredTagsCb">{% trans "keep ignored questions hidden" %}</label> <p> </div> diff --git a/forum/skins/default/templates/tags.html b/forum/skins/default/templates/tags.html index 50f90fb1..50f90fb1 100644..100755 --- a/forum/skins/default/templates/tags.html +++ b/forum/skins/default/templates/tags.html diff --git a/forum/skins/default/templates/user.html b/forum/skins/default/templates/user.html index 5931f31c..5931f31c 100644..100755 --- a/forum/skins/default/templates/user.html +++ b/forum/skins/default/templates/user.html diff --git a/forum/skins/default/templates/user_edit.html b/forum/skins/default/templates/user_edit.html index 040ebff4..040ebff4 100644..100755 --- a/forum/skins/default/templates/user_edit.html +++ b/forum/skins/default/templates/user_edit.html diff --git a/forum/skins/default/templates/user_email_subscriptions.html b/forum/skins/default/templates/user_email_subscriptions.html index c0204cbc..c0204cbc 100644..100755 --- a/forum/skins/default/templates/user_email_subscriptions.html +++ b/forum/skins/default/templates/user_email_subscriptions.html diff --git a/forum/skins/default/templates/user_favorites.html b/forum/skins/default/templates/user_favorites.html index 9db01e9a..9db01e9a 100644..100755 --- a/forum/skins/default/templates/user_favorites.html +++ b/forum/skins/default/templates/user_favorites.html diff --git a/forum/skins/default/templates/user_footer.html b/forum/skins/default/templates/user_footer.html index ee347742..ee347742 100644..100755 --- a/forum/skins/default/templates/user_footer.html +++ b/forum/skins/default/templates/user_footer.html diff --git a/forum/skins/default/templates/user_info.html b/forum/skins/default/templates/user_info.html index c550e13f..b0fd246a 100644..100755 --- a/forum/skins/default/templates/user_info.html +++ b/forum/skins/default/templates/user_info.html @@ -45,9 +45,9 @@ <span class="user-edit-link"><a href="{% url users %}{{ view_user.id }}/{% trans "edit/" %}">{% trans "update profile" %}</a></span> {% endif %} {% separator %} - {% if request.user.has_usable_password %} - <a href="{% url user_changepw %}">change password</a> - {% endif %} + {% ifequal request.user view_user %} + <a href="{% url user_authsettings %}">authentication settings</a> + {% endifequal %} {% endjoinitems %} </td> </tr> diff --git a/forum/skins/default/templates/user_recent.html b/forum/skins/default/templates/user_recent.html index b704ab25..b704ab25 100644..100755 --- a/forum/skins/default/templates/user_recent.html +++ b/forum/skins/default/templates/user_recent.html diff --git a/forum/skins/default/templates/user_reputation.html b/forum/skins/default/templates/user_reputation.html index 776935ae..776935ae 100644..100755 --- a/forum/skins/default/templates/user_reputation.html +++ b/forum/skins/default/templates/user_reputation.html diff --git a/forum/skins/default/templates/user_responses.html b/forum/skins/default/templates/user_responses.html index c4f4ffed..c4f4ffed 100644..100755 --- a/forum/skins/default/templates/user_responses.html +++ b/forum/skins/default/templates/user_responses.html diff --git a/forum/skins/default/templates/user_stats.html b/forum/skins/default/templates/user_stats.html index a3f88131..5ad1d71b 100644..100755 --- a/forum/skins/default/templates/user_stats.html +++ b/forum/skins/default/templates/user_stats.html @@ -124,7 +124,7 @@ <tr> <td width="180" style="line-height:35px"> {% for award in awards %} - <a href="{% url badges %}{{award.id}}/{{award.name}}" title="{{ award.description }}" class="medal"><span class="badge{{ award.type }}">●</span> {{ award.name }}</a><span class="tag-number"> ✕ {{ award.count|intcomma }}</span><br/> + <a href="{% url badges %}{{award.id}}/{{award.name}}" title="{{ award.description }}" class="medal"><span class="badge{{ award.type }}">●</span> {{ award.name }}</a><span class="tag-number"> × {{ award.count|intcomma }}</span><br/> {% if forloop.counter|divisibleby:"6" %} </td> <td width="180" style="line-height:35px"> diff --git a/forum/skins/default/templates/user_tabs.html b/forum/skins/default/templates/user_tabs.html index 908e8430..136a1eaa 100644..100755 --- a/forum/skins/default/templates/user_tabs.html +++ b/forum/skins/default/templates/user_tabs.html @@ -4,28 +4,28 @@ <div class="tabBar"> <div class="tabsA"> <a id="stats" {% ifequal tab_name "stats" %}class="on"{% endifequal %} - title="{% trans "User profile" %}" href="{% url users %}{{view_user.id}}/{{view_user.username}}?sort=stats">{% trans "overview" %}</a> + title="{% trans "User profile" %}" href="/users/{{ view_user.id }}/{{ view_user.username|slugify }}/?sort=stats">{% trans "overview" %}</a> <a id="recent" {% ifequal tab_name "recent" %}class="on"{% endifequal %} - title="{% trans "recent activity" %}" href="{% url users %}{{view_user.id}}/{{view_user.username}}?sort=recent">{% trans "recent activity" %}</a> + title="{% trans "recent activity" %}" href="/users/{{ view_user.id }}/{{ view_user.username|slugify }}?sort=recent">{% trans "recent activity" %}</a> {% if request.user|is_user_self:view_user %} <a id="responses" {% ifequal tab_name "responses" %}class="on"{% endifequal %} title="{% trans "comments and answers to others questions" %}" - href="{% url users %}{{view_user.id}}/{{view_user.username}}?sort=responses">{% trans "responses" %}</a> + href="/users/{{ view_user.id }}/{{ view_user.username|slugify }}?sort=responses">{% trans "responses" %}</a> {% endif %} <a id="reputation" {% ifequal tab_name "reputation" %}class="on"{% endifequal %} title="{% trans "graph of user reputation" %}" - href="{% url users %}{{view_user.id}}/{{view_user.username}}?sort=reputation">{% trans "reputation history" %}</a> + href="/users/{{ view_user.id }}/{{ view_user.username|slugify }}?sort=reputation">{% trans "reputation history" %}</a> {% if request.user|can_view_user_votes:view_user %} <a id="votes" {% ifequal tab_name "votes" %}class="on"{% endifequal %} - title="{% trans "user vote record" %}" href="{% url users %}{{view_user.id}}/{{view_user.username}}?sort=votes">{% trans "casted votes" %}</a> + title="{% trans "user vote record" %}" href="/users/{{ view_user.id }}/{{ view_user.username|slugify }}?sort=votes">{% trans "casted votes" %}</a> {% endif %} <a id="favorites" {% ifequal tab_name "favorites" %}class="on"{% endifequal %} title="{% trans "questions that user selected as his/her favorite" %}" - href="{% url users %}{{view_user.id}}/{{view_user.username}}?sort=favorites">{% trans "favorites" %}</a> + href="/users/{{ view_user.id }}/{{ view_user.username|slugify }}?sort=favorites">{% trans "favorites" %}</a> {% if request.user|can_view_user_preferences:view_user %} <a id="email_subscriptions" {% ifequal tab_name "email_subscriptions" %}class="on"{% endifequal %} title="{% trans "email subscription settings" %}" - href="{% url users %}{{view_user.id}}/{{view_user.username}}?sort=email_subscriptions">{% trans "email subscriptions" %}</a> + href="/users/{{ view_user.id }}/{{ view_user.username|slugify }}?sort=email_subscriptions">{% trans "email subscriptions" %}</a> {% endif %} </div> </div> diff --git a/forum/skins/default/templates/user_votes.html b/forum/skins/default/templates/user_votes.html index b56aab01..b56aab01 100644..100755 --- a/forum/skins/default/templates/user_votes.html +++ b/forum/skins/default/templates/user_votes.html diff --git a/forum/skins/default/templates/users.html b/forum/skins/default/templates/users.html index 3a59b0c0..f24b60a1 100644..100755 --- a/forum/skins/default/templates/users.html +++ b/forum/skins/default/templates/users.html @@ -47,8 +47,8 @@ <div class="user"> <ul> - <li class="thumb"><a href="{{ user.get_profile_url }}">{% gravatar user 32 %}</a></li> - <li><a href="{{ user.get_profile_url }}">{{user.username}}</a></li> + <li class="thumb"><a href="/users/{{ user.id }}/{{ user.username|slugify }}/">{% gravatar user 32 %}</a></li> + <li><a href="/users/{{ user.id }}/{{ user.username|slugify }}/">{{user.username}}</a></li> <li>{% get_score_badge user %}</li> </ul> </div> diff --git a/forum/skins/default/templates/users_questions.html b/forum/skins/default/templates/users_questions.html index 8049d832..8049d832 100644..100755 --- a/forum/skins/default/templates/users_questions.html +++ b/forum/skins/default/templates/users_questions.html diff --git a/forum/templatetags/__init__.py b/forum/templatetags/__init__.py index e69de29b..e69de29b 100644..100755 --- a/forum/templatetags/__init__.py +++ b/forum/templatetags/__init__.py diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py index 22ec0109..3644fdc3 100644..100755 --- a/forum/templatetags/extra_filters.py +++ b/forum/templatetags/extra_filters.py @@ -1,5 +1,4 @@ from django import template -from django.core import serializers from forum import auth import logging @@ -92,7 +91,3 @@ def cnprog_intword(number): return number except: return number - -@register.filter -def json_serialize(object): - return serializers.serialize('json',object) diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py index 26c52b8d..6dab868b 100644..100755 --- a/forum/templatetags/extra_tags.py +++ b/forum/templatetags/extra_tags.py @@ -13,6 +13,7 @@ from forum.models import Question, Answer, QuestionRevision, AnswerRevision from django.utils.translation import ugettext as _ from django.utils.translation import ungettext from django.conf import settings +from django.template.defaulttags import url as default_url from forum import skins register = template.Library() @@ -355,3 +356,27 @@ def blockmedia(parser,token): if next.contents == 'endblockmedia': break return BlockMediaUrlNode(nodelist) + +class FullUrlNode(template.Node): + def __init__(self, default_node): + self.default_node = default_node + + def render(self, context): + domain = settings.APP_URL + #protocol = getattr(settings, "PROTOCOL", "http") + path = self.default_node.render(context) + return "%s%s" % (domain, path) + +@register.tag(name='fullurl') +def fullurl(parser, token): + default_node = default_url(parser, token) + return FullUrlNode(default_node) + +@register.simple_tag +def fullmedia(url): + domain = settings.APP_URL + #protocol = getattr(settings, "PROTOCOL", "http") + path = media(url) + return "%s%s" % (domain, path) + + diff --git a/forum/templatetags/smart_if.py b/forum/templatetags/smart_if.py index ca3b43fe..ca3b43fe 100644..100755 --- a/forum/templatetags/smart_if.py +++ b/forum/templatetags/smart_if.py diff --git a/forum/upfiles/README b/forum/upfiles/README index 17bf8ecb..17bf8ecb 100644..100755 --- a/forum/upfiles/README +++ b/forum/upfiles/README diff --git a/forum/urls.py b/forum/urls.py index fd9ebdc1..3e99af41 100644..100755 --- a/forum/urls.py +++ b/forum/urls.py @@ -29,7 +29,7 @@ urlpatterns = patterns('', {'document_root': os.path.join(APP_PATH,'upfiles').replace('\\','/')}, name='uploaded_file', ), - url(r'^%s/$' % _('signin/'), 'django_authopenid.views.signin', name='signin'), + #url(r'^%s/$' % _('signin/'), 'django_authopenid.views.signin', name='signin'), url(r'^%s$' % _('about/'), app.meta.about, name='about'), url(r'^%s$' % _('faq/'), app.meta.faq, name='faq'), url(r'^%s$' % _('privacy/'), app.meta.privacy, name='privacy'), @@ -76,7 +76,7 @@ urlpatterns = patterns('', url(r'^%s$' % _('users/'),app.users.users, name='users'), url(r'^%s(?P<id>\d+)/$' % _('moderate-user/'), app.users.moderate_user, name='moderate_user'), url(r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), app.users.edit_user, name='edit_user'), - url(r'^%s(?P<id>\d+)//*' % _('users/'), app.users.user, name='user'), + url(r'^%s(?P<id>\d+)/(?P<slug>.+)/$' % _('users/'), app.users.user, name='user_profile'), url(r'^%s$' % _('badges/'),app.meta.badges, name='badges'), url(r'^%s(?P<id>\d+)//*' % _('badges/'), app.meta.badge, name='badge'), url(r'^%s%s$' % (_('messages/'), _('markread/')),app.commands.read_message, name='read_message'), @@ -86,9 +86,28 @@ urlpatterns = patterns('', url(r'^%s$' % _('upload/'), app.writers.upload, name='upload'), url(r'^%s$' % _('search/'), app.readers.search, name='search'), url(r'^%s$' % _('feedback/'), app.meta.feedback, name='feedback'), - (r'^%sfb/' % _('account/'), include('fbconnect.urls')), - (r'^%s' % _('account/'), include('django_authopenid.urls')), + #(r'^%sfb/' % _('account/'), include('fbconnect.urls')), + #(r'^%s' % _('account/'), include('django_authopenid.urls')), (r'^i18n/', include('django.conf.urls.i18n')), + + url(r'^%s%s$' % (_('account/'), _('signin/')), app.auth.signin_page, name='auth_signin'), + url(r'^%s%s$' % (_('account/'), _('signout/')), app.auth.signout, name='user_signout'), + url(r'^%s%s(?P<action>\w+)/$' % (_('account/'), _('signin/')), app.auth.signin_page, name='auth_action_signin'), + url(r'^%s(?P<provider>\w+)/%s$' % (_('account/'), _('signin/')), app.auth.prepare_provider_signin, name='auth_provider_signin'), + url(r'^%s(?P<provider>\w+)/%s$' % (_('account/'), _('done/')), app.auth.process_provider_signin, name='auth_provider_done'), + url(r'^%s%s$' % (_('account/'), _('register/')), app.auth.external_register, name='auth_external_register'), + url(r'^%s%s(?P<user>\d+)/(?P<code>.+)/$' % (_('account/'), _('validate/')), app.auth.validate_email, name="auth_validate_email"), + url(r'^%s%s$' % (_('account/'), _('tempsignin/')), app.auth.request_temp_login, name="auth_request_tempsignin"), + url(r'^%s%s(?P<user>\d+)/(?P<code>.+)/$' % (_('account/'), _('tempsignin/')), app.auth.temp_signin, name="auth_tempsignin"), + url(r'^%s%s$' % (_('account/'), _('authsettings/')), app.auth.auth_settings, name='user_authsettings'), + url(r'^%s%s(?P<id>\d+)/%s$' % (_('account/'), _('providers/'), _('remove/')), app.auth.remove_external_provider, name='user_remove_external_provider'), + url(r'^%s%s%s$' % (_('account/'), _('providers/'), _('add/')), app.auth.signin_page, name='user_add_external_provider'), + + #url(r'^%s%s%s$' % (_('accounts/'), _('password/'), _('confirm/')), app.user.confirmchangepw, name='user_confirmchangepw'), + #url(r'^%s$' % _('account/'), app.users.account_settings, name='user_account_settings'), + #url(r'^%s$' % _('delete/'), app.users.delete, name='user_delete'), + + url(r'^feeds/rss/$', RssLastestQuestionsFeed, name="latest_questions_feed"), ) from forum.modules import get_modules_script diff --git a/forum/user_messages/__init__.py b/forum/user_messages/__init__.py index 0136c888..0136c888 100644..100755 --- a/forum/user_messages/__init__.py +++ b/forum/user_messages/__init__.py diff --git a/forum/user_messages/context_processors.py b/forum/user_messages/context_processors.py index 2bf26269..5f7b857c 100644..100755 --- a/forum/user_messages/context_processors.py +++ b/forum/user_messages/context_processors.py @@ -17,6 +17,9 @@ def user_messages (request): #if request.user.is_authenticated(): #else: # messages = LazyMessages(request) + #import inspect + #print inspect.stack()[1] + #print messages return { 'user_messages': messages } class LazyMessages (StrAndUnicode): diff --git a/forum/utils/__init__.py b/forum/utils/__init__.py index e69de29b..e69de29b 100644..100755 --- a/forum/utils/__init__.py +++ b/forum/utils/__init__.py diff --git a/forum/utils/cache.py b/forum/utils/cache.py index 410c0662..6341392e 100644..100755 --- a/forum/utils/cache.py +++ b/forum/utils/cache.py @@ -3,7 +3,7 @@ import itertools from django.contrib.contenttypes.models import ContentType -from lanai.utils.lists import flatten +from forum.utils.lists import flatten def fetch_model_dict(model, ids, fields=None): """ diff --git a/forum/utils/decorators.py b/forum/utils/decorators.py index e4e7acb3..e4e7acb3 100644..100755 --- a/forum/utils/decorators.py +++ b/forum/utils/decorators.py diff --git a/forum/utils/diff.py b/forum/utils/diff.py index d741d788..d741d788 100644..100755 --- a/forum/utils/diff.py +++ b/forum/utils/diff.py diff --git a/forum/utils/email.py b/forum/utils/email.py new file mode 100755 index 00000000..dc712572 --- /dev/null +++ b/forum/utils/email.py @@ -0,0 +1,21 @@ +from django.core.mail import send_mail, EmailMultiAlternatives +from django.conf import settings +from django.template import loader, Context +from django.utils.html import strip_tags +from threading import Thread + +def send_email(subject, recipients, template, context={}, sender=settings.DEFAULT_FROM_EMAIL, txt_template=None): + context['settings'] = settings + html_body = loader.get_template(template).render(Context(context)) + + if txt_template is None: + txt_body = strip_tags(html_body) + else: + txt_body = loader.get_template(txt_template).render(Context(context)) + + msg = EmailMultiAlternatives(subject, txt_body, sender, recipients) + msg.attach_alternative(html_body, "text/html") + + thread = Thread(target=EmailMultiAlternatives.send, args=[msg]) + thread.setDaemon(True) + thread.start() diff --git a/forum/utils/forms.py b/forum/utils/forms.py index 82f10b28..d8d04d05 100644..100755 --- a/forum/utils/forms.py +++ b/forum/utils/forms.py @@ -145,6 +145,10 @@ class SetPasswordForm(forms.Form): error_messages={'required':_('please, retype your password'), 'nomatch':_('sorry, entered passwords did not match, please try again')}, ) + + def __init__(self, data=None, user=None, *args, **kwargs): + super(SetPasswordForm, self).__init__(data, *args, **kwargs) + def clean_password2(self): """ Validates that the two password inputs match. diff --git a/forum/utils/html.py b/forum/utils/html.py index 25a74a4a..25a74a4a 100644..100755 --- a/forum/utils/html.py +++ b/forum/utils/html.py diff --git a/forum/utils/lists.py b/forum/utils/lists.py index bbcfae98..f69c8f20 100644..100755 --- a/forum/utils/lists.py +++ b/forum/utils/lists.py @@ -1,5 +1,9 @@ """Utilities for working with lists and sequences.""" +class LazyList(list): + def __init__(self, get_data): + self.data = get_data + def flatten(x): """ Returns a single, flat list which contains all elements retrieved diff --git a/forum/utils/odict.py b/forum/utils/odict.py index 2c8391d7..2c8391d7 100644..100755 --- a/forum/utils/odict.py +++ b/forum/utils/odict.py diff --git a/forum/utils/time.py b/forum/utils/time.py new file mode 100755 index 00000000..39e01d0f --- /dev/null +++ b/forum/utils/time.py @@ -0,0 +1,4 @@ +import datetime + +def one_day_from_now(): + return datetime.datetime.now() + datetime.timedelta(days=1) diff --git a/forum/views/README b/forum/views/README index 7b6201cd..5416f88c 100644..100755 --- a/forum/views/README +++ b/forum/views/README @@ -9,4 +9,4 @@ users.py - user views - user listing and profiles meta.py - privacy, about, faq, feedback, logout, badges -books.py - book views - to be moved to a books extension +auth.py - Authentication related views diff --git a/forum/views/__init__.py b/forum/views/__init__.py index 291fee2a..a5f6f99d 100644..100755 --- a/forum/views/__init__.py +++ b/forum/views/__init__.py @@ -3,3 +3,4 @@ import writers import commands import users import meta +import auth diff --git a/forum/views/auth.py b/forum/views/auth.py new file mode 100644 index 00000000..72b0af29 --- /dev/null +++ b/forum/views/auth.py @@ -0,0 +1,359 @@ +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +from django.http import HttpResponseRedirect, Http404 +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext as _ +from django.utils.http import urlquote_plus +from django.contrib.auth.decorators import login_required +from django.contrib.auth import login, logout +from django.http import get_host +import types +import datetime + +from forum.models import AuthKeyUserAssociation, ValidationHash +from forum.authentication.forms import SimpleRegistrationForm, SimpleEmailSubscribeForm, \ + TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm +from forum.utils.email import send_email + +from forum.authentication.base import InvalidAuthentication +from forum.authentication import AUTH_PROVIDERS + +from forum.models import Question, Answer + +def signin_page(request, action=None): + if action is None: + request.session['on_signin_url'] = request.META.get('HTTP_REFERER', '/') + else: + request.session['on_signin_action'] = action + request.session['on_signin_url'] = reverse('auth_action_signin', kwargs={'action': action}) + + all_providers = [provider.context for provider in AUTH_PROVIDERS.values()] + + sort = lambda c1, c2: c1.weight - c2.weight + can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user + + bigicon_providers = sorted([ + context for context in all_providers if context.mode == 'BIGICON' and can_show(context) + ], sort) + + smallicon_providers = sorted([ + context for context in all_providers if context.mode == 'SMALLICON' and can_show(context) + ], sort) + + stackitem_providers = sorted([ + context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context) + ], sort) + + try: + msg = request.session['auth_error'] + del request.session['auth_error'] + except: + msg = None + + return render_to_response( + 'auth/signin.html', + { + 'msg': msg, + 'all_providers': all_providers, + 'bigicon_providers': bigicon_providers, + 'stackitem_providers': stackitem_providers, + 'smallicon_providers': smallicon_providers, + }, + RequestContext(request)) + +def prepare_provider_signin(request, provider): + force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes' + request.session['force_email_request'] = force_email_request + + if provider in AUTH_PROVIDERS: + provider_class = AUTH_PROVIDERS[provider].consumer + + try: + request_url = provider_class.prepare_authentication_request(request, + reverse('auth_provider_done', kwargs={'provider': provider})) + + return HttpResponseRedirect(request_url) + except NotImplementedError, e: + return process_provider_signin(request, provider) + except InvalidAuthentication, e: + request.session['auth_error'] = e.message + + return HttpResponseRedirect(reverse('auth_signin')) + + +def process_provider_signin(request, provider): + if provider in AUTH_PROVIDERS: + provider_class = AUTH_PROVIDERS[provider].consumer + + try: + assoc_key = provider_class.process_authentication_request(request) + except InvalidAuthentication, e: + request.session['auth_error'] = e.message + return HttpResponseRedirect(reverse('auth_signin')) + + if request.user.is_authenticated(): + if isinstance(assoc_key, (type, User)): + if request.user != assoc_key: + request.session['auth_error'] = _("Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again.") + else: + request.session['auth_error'] = _("You are already logged in with that user.") + else: + try: + assoc = AuthKeyUserAssociation.objects.get(key=assoc_key) + if assoc.user == request.user: + request.session['auth_error'] = _("These login credentials are already associated with your account.") + else: + request.session['auth_error'] = _("Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again.") + except: + uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider) + uassoc.save() + request.user.message_set.create(message=_('The new credentials are now associated with your account')) + return HttpResponseRedirect(reverse('user_authsettings')) + + return HttpResponseRedirect(reverse('auth_signin')) + else: + if isinstance(assoc_key, (type, User)): + return login_and_forward(request, assoc_key) + + try: + assoc = AuthKeyUserAssociation.objects.get(key=assoc_key) + user_ = assoc.user + return login_and_forward(request, user_) + except: + request.session['assoc_key'] = assoc_key + request.session['auth_provider'] = provider + return HttpResponseRedirect(reverse('auth_external_register')) + + return HttpResponseRedirect(reverse('auth_signin')) + +def external_register(request): + if request.method == 'POST' and 'bnewaccount' in request.POST: + form1 = SimpleRegistrationForm(request.POST) + email_feeds_form = SimpleEmailSubscribeForm(request.POST) + + if (form1.is_valid() and email_feeds_form.is_valid()): + user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email']) + user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email'] + user_.set_unusable_password() + + user_.save() + + if not user_.email_isvalid: + send_validation_email(user_) + + try: + assoc_key = request.session['assoc_key'] + auth_provider = request.session['auth_provider'] + except: + request.session['auth_error'] = _("Oops, something went wrong in the middle of this process. Please try again.") + return HttpResponseRedirect(request.session.get('on_signin_url', reverse('auth_signin'))) + + uassoc = AuthKeyUserAssociation(user=user_, key=request.session['assoc_key'], provider=request.session['auth_provider']) + uassoc.save() + + email_feeds_form.save(user_) + + del request.session['assoc_key'] + del request.session['auth_provider'] + + if user_.email_isvalid: + return login_and_forward(request, user_) + else: + return HttpResponseRedirect(reverse('index')) + else: + provider_class = AUTH_PROVIDERS[request.session['auth_provider']].consumer + user_data = provider_class.get_user_data(request.session['assoc_key']) + + username = user_data.get('username', '') + email = user_data.get('email', '') + + if not email: + email = request.session.get('auth_email_request', '') + + if email: + request.session['auth_validated_email'] = email + + form1 = SimpleRegistrationForm(initial={ + 'next': '/', + 'username': username, + 'email': email, + }) + email_feeds_form = SimpleEmailSubscribeForm() + + provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context + + return render_to_response('auth/complete.html', { + 'form1': form1, + 'email_feeds_form': email_feeds_form, + 'provider':mark_safe(provider_context.human_name), + 'login_type':provider_context.id, + 'gravatar_faq_url':reverse('faq') + '#gravatar', + }, context_instance=RequestContext(request)) + +def request_temp_login(request): + if request.method == 'POST': + form = TemporaryLoginRequestForm(request.POST) + + if form.is_valid(): + user = form.user_cache + + try: + hash = get_object_or_404(ValidationHash, user=user, type='templogin') + if hash.expiration < datetime.datetime.now(): + hash.delete() + return request_temp_login(request) + except: + hash = ValidationHash.objects.create_new(user, 'templogin', [user.id]) + + send_email(_("Temporary login link"), [user.email], "auth/temp_login_email.html", { + 'temp_login_code': hash, + 'user': user + }) + + request.user.message_set.create(message=_("An email has been sent with your temporary login key")) + + return HttpResponseRedirect(reverse('index')) + else: + form = TemporaryLoginRequestForm() + + return render_to_response( + 'auth/temp_login_request.html', {'form': form}, + context_instance=RequestContext(request)) + +def temp_signin(request, user, code): + user = get_object_or_404(User, id=user) + + if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])): + return login_and_forward(request, user, reverse('user_authsettings'), + _("You are logged in with a temporary access key, please take the time to fix your issue with authentication.")) + else: + raise Http404() + +def send_validation_email(user): + hash = ValidationHash.objects.create_new(user, 'email', [user.email]) + send_email(_("Email Validation"), [user.email], "auth/email_validation.html", { + 'validation_code': hash, + 'user': user + }) + +def validate_email(request, user, code): + user = get_object_or_404(User, id=user) + + if (ValidationHash.objects.validate(code, user, 'email', [user.email])): + user.email_isvalid = True + user.save() + return login_and_forward(request, user, None, _("Thank you, your email is now validated.")) + else: + raise Http404() + +@login_required +def auth_settings(request): + """ + change password view. + + url : /changepw/ + template: authopenid/changepw.html + """ + user_ = request.user + auth_keys = user_.auth_keys.all() + + if user_.has_usable_password(): + FormClass = ChangePasswordForm + else: + FormClass = SetPasswordForm + + if request.POST: + form = FormClass(request.POST, user=user_) + if form.is_valid(): + if user_.has_usable_password(): + request.user.message_set.create(message=_("Your password was changed")) + else: + request.user.message_set.create(message=_("New password set")) + form = ChangePasswordForm(user=user_) + + user_.set_password(form.cleaned_data['password1']) + user_.save() + return HttpResponseRedirect(reverse('user_authsettings')) + else: + form = FormClass(user=user_) + + auth_keys_list = [] + + for k in auth_keys: + provider = AUTH_PROVIDERS.get(k.provider, None) + + if provider is not None: + name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k)) + else: + from forum.authentication.base import ConsumerTemplateContext + "unknown: %s" % ConsumerTemplateContext.readable_key(k) + + auth_keys_list.append({ + 'name': name, + 'id': k.id + }) + + return render_to_response('auth/auth_settings.html', { + 'form': form, + 'has_password': user_.has_usable_password(), + 'auth_keys': auth_keys_list, + }, context_instance=RequestContext(request)) + +def remove_external_provider(request, id): + association = get_object_or_404(AuthKeyUserAssociation, id=id) + request.user.message_set.create(message=_("You removed the association with %s") % association.provider) + association.delete() + return HttpResponseRedirect(reverse('user_authsettings')) + +def newquestion_signin_action(user): + question = Question.objects.filter(author=user).order_by('-added_at')[0] + return question.get_absolute_url() + +def newanswer_signin_action(user): + answer = Answer.objects.filter(author=user).order_by('-added_at')[0] + return answer.get_absolute_url() + +POST_SIGNIN_ACTIONS = { + 'newquestion': newquestion_signin_action, + 'newanswer': newanswer_signin_action, +} + +def login_and_forward(request, user, forward=None, message=None): + old_session = request.session.session_key + user.backend = "django.contrib.auth.backends.ModelBackend" + login(request, user) + + from forum.models import user_logged_in + user_logged_in.send(user=user,session_key=old_session,sender=None) + + if not forward: + signin_action = request.session.get('on_signin_action', None) + if not signin_action: + forward = request.session.get('on_signin_url', None) + + if not forward: + forward = reverse('index') + else: + try: + forward = POST_SIGNIN_ACTIONS[signin_action](user) + except: + forward = reverse('index') + + if message is None: + message = _("Welcome back %s, you are now logged in") % user.username + + request.user.message_set.create(message=message) + return HttpResponseRedirect(forward) + +@login_required +def signout(request): + """ + signout from the website. Remove openid from session and kill it. + + url : /signout/" + """ + + logout(request) + return HttpResponseRedirect(reverse('index'))
\ No newline at end of file diff --git a/forum/views/commands.py b/forum/views/commands.py index 88c2c077..ca6569e2 100644..100755 --- a/forum/views/commands.py +++ b/forum/views/commands.py @@ -13,7 +13,7 @@ from django.contrib.auth.decorators import login_required from forum.utils.decorators import ajax_method, ajax_login_required import logging -def vote(request, id):#refactor - pretty incomprehensible view used by various ajax calls +def vote(request, id):#todo: pretty incomprehensible view used by various ajax calls #issues: this subroutine is too long, contains many magic numbers and other issues #it's called "vote" but many actions processed here have nothing to do with voting """ @@ -106,23 +106,12 @@ def vote(request, id):#refactor - pretty incomprehensible view used by various a # favorite elif vote_type == '4': has_favorited = False - fav_questions = FavoriteQuestion.objects.filter(question=question) - # if the same question has been favorited before, then delete it - if fav_questions is not None: - for item in fav_questions: - if item.user == request.user: - item.delete() - response_data['status'] = 1 - response_data['count'] = len(fav_questions) - 1 - if response_data['count'] < 0: - response_data['count'] = 0 - has_favorited = True - # if above deletion has not been executed, just insert a new favorite question - if not has_favorited: - new_item = FavoriteQuestion(question=question, user=request.user) - new_item.save() - response_data['count'] = FavoriteQuestion.objects.filter(question=question).count() - Question.objects.update_favorite_count(question) + fave = request.user.toggle_favorite_question(question) + response_data['count'] = FavoriteQuestion.objects.filter( + question = question + ).count() + if fave == False: + response_data['status'] = 1 elif vote_type in ['1', '2', '5', '6']: post_id = id @@ -141,7 +130,13 @@ def vote(request, id):#refactor - pretty incomprehensible view used by various a elif not __can_vote(vote_score, request.user): response_data['allowed'] = -2 elif post.votes.filter(user=request.user).count() > 0: + #todo: I think we have a bug here + #we need to instead select vote on that particular post + #not just the latest vote, although it is a good shortcut. + #The problem is that this vote is deleted in one of + #the on...Canceled() functions vote = post.votes.filter(user=request.user)[0] + # get latest vote by the current user # unvote should be less than certain time if (datetime.datetime.now().day - vote.voted_at.day) >= auth.VOTE_RULES['scope_deny_unvote_days']: response_data['status'] = 2 @@ -189,8 +184,6 @@ def vote(request, id):#refactor - pretty incomprehensible view used by various a item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now()) auth.onFlaggedItem(item, post, request.user) response_data['count'] = post.offensive_flag_count - # send signal when question or answer be marked offensive - mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user) elif vote_type in ['9', '10']: post = question post_id = id @@ -206,7 +199,6 @@ def vote(request, id):#refactor - pretty incomprehensible view used by various a response_data['status'] = 1 else: auth.onDeleted(post, request.user) - delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user) elif vote_type == '11':#subscribe q updates user = request.user if user.is_authenticated(): diff --git a/forum/views/meta.py b/forum/views/meta.py index b4c7a37f..6417e8f5 100644..100755 --- a/forum/views/meta.py +++ b/forum/views/meta.py @@ -15,7 +15,7 @@ def about(request): def faq(request): data = { 'gravatar_faq_url': reverse('faq') + '#gravatar', - 'send_email_key_url': reverse('send_email_key'), + #'send_email_key_url': reverse('send_email_key'), 'ask_question_url': reverse('ask'), } return render_to_response('faq.html', data, context_instance=RequestContext(request)) @@ -58,7 +58,7 @@ def logout(request):#refactor/change behavior? }, context_instance=RequestContext(request)) def badges(request):#user status/reputation system - badges = Badge.objects.all().order_by('type') + badges = Badge.objects.all().order_by('name') my_badges = [] if request.user.is_authenticated(): my_badges = Award.objects.filter(user=request.user).values('badge_id') diff --git a/forum/views/readers.py b/forum/views/readers.py index 6b0da476..938fa133 100644 --- a/forum/views/readers.py +++ b/forum/views/readers.py @@ -14,6 +14,7 @@ from django.utils.translation import ugettext as _ from django.template.defaultfilters import slugify from django.core.urlresolvers import reverse from django.utils.datastructures import SortedDict +from django.views.decorators.cache import cache_page from forum.utils.html import sanitize_html from markdown2 import Markdown @@ -87,7 +88,7 @@ def index(request):#generates front page - shows listing of questions sorted in } view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest') - pagesize = request.session.get("pagesize",QUESTIONS_PAGE_SIZE) + pagesize = QUESTIONS_PAGE_SIZE #request.session.get("pagesize",QUESTIONS_PAGE_SIZE) try: page = int(request.GET.get('page', '1')) except ValueError: @@ -145,7 +146,7 @@ def questions(request, tagname=None, unanswered=False):#a view generating listin # Set flag to False by default. If it is equal to True, then need to be saved. pagesize_changed = False # get pagesize from session, if failed then get default value - pagesize = request.session.get("pagesize",QUESTIONS_PAGE_SIZE) + pagesize = QUESTIONS_PAGE_SIZE #request.session.get("pagesize",QUESTIONS_PAGE_SIZE) try: page = int(request.GET.get('page', '1')) except ValueError: diff --git a/forum/views/users.py b/forum/views/users.py index cc05c19e..ff92803c 100644..100755 --- a/forum/views/users.py +++ b/forum/views/users.py @@ -6,11 +6,13 @@ from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext -from django.http import HttpResponse, HttpResponseForbidden +from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect, Http404 from django.utils.translation import ugettext as _ +from django.utils.http import urlquote_plus from django.utils.html import strip_tags from django.core.urlresolvers import reverse -from forum.forms import *#incomplete list is EditUserForm, ModerateUserForm, TagFilterSelectionForm, +from forum.forms import *#incomplete list is EditUserForm, ModerateUserForm, TagFilterSelectionForm, +from forum.utils.html import sanitize_html from forum import auth import calendar from django.contrib.contenttypes.models import ContentType @@ -98,6 +100,14 @@ def moderate_user(request, id): response = HttpResponseForbidden(mimetype="application/json") return response +def set_new_email(user, new_email, nomessage=False): + if new_email != user.email: + user.email = new_email + user.email_isvalid = False + user.save() + #if settings.EMAIL_VALIDATION == 'on': + # send_new_email_key(user,nomessage=nomessage) + @login_required def edit_user(request, id): user = get_object_or_404(User, id=id) @@ -108,7 +118,6 @@ def edit_user(request, id): if form.is_valid(): new_email = sanitize_html(form.cleaned_data['email']) - from django_authopenid.views import set_new_email set_new_email(user, new_email) #user.username = sanitize_html(form.cleaned_data['username']) @@ -938,10 +947,32 @@ USER_TEMPLATE_VIEWS = ( ) ) -def user(request, id): +def user(request, id, slug=None): 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.views import users func = user_view.view_func return func(request, id, user_view) +@login_required +def account_settings(request): + """ + index pages to changes some basic account settings : + - change password + - change email + - associate a new openid + - delete account + + url : / + + template : authopenid/settings.html + """ + logging.debug('') + msg = request.GET.get('msg', '') + is_openid = False + + return render_to_response('account_settings.html', { + 'msg': msg, + 'is_openid': is_openid + }, context_instance=RequestContext(request)) + diff --git a/forum/views/writers.py b/forum/views/writers.py index c8ddc079..a9406fdc 100644..100755 --- a/forum/views/writers.py +++ b/forum/views/writers.py @@ -13,8 +13,6 @@ from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse from django.core.exceptions import PermissionDenied -from forum.utils.html import sanitize_html -from markdown2 import Markdown from forum.forms import * from forum.models import * from forum.auth import * @@ -34,8 +32,6 @@ QUESTIONS_PAGE_SIZE = 10 # used in answers ANSWERS_PAGE_SIZE = 10 -markdowner = Markdown(html4tags=True) - def upload(request):#ajax upload file to a question or answer class FileTypeNotAllow(Exception): pass @@ -94,12 +90,16 @@ def ask(request):#view used to ask a new question if form.is_valid(): added_at = datetime.datetime.now() + #todo: move this to clean_title title = strip_tags(form.cleaned_data['title'].strip()) wiki = form.cleaned_data['wiki'] + #todo: move this to clean_tagnames tagnames = form.cleaned_data['tags'].strip() text = form.cleaned_data['text'] - html = sanitize_html(markdowner.convert(text)) - summary = strip_tags(html)[:120] + + #todo: move this to AskForm.clean_text + #todo: make custom MarkDownField + text = form.cleaned_data['text'] if request.user.is_authenticated(): author = request.user @@ -110,14 +110,14 @@ def ask(request):#view used to ask a new question added_at = added_at, wiki = wiki, tagnames = tagnames, - summary = summary, - text = text + text = text, ) return HttpResponseRedirect(question.get_absolute_url()) else: request.session.flush() session_key = request.session.session_key + summary = strip_tags(text)[:120] question = AnonymousQuestion( session_key = session_key, title = title, @@ -129,7 +129,7 @@ def ask(request):#view used to ask a new question ip_addr = request.META['REMOTE_ADDR'], ) question.save() - return HttpResponseRedirect(reverse('user_signin_new_question')) + return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newquestion'})) else: form = AskForm() @@ -162,32 +162,11 @@ def _retag_question(request, question):#non-url subview of edit question - just form = RetagQuestionForm(question, request.POST) if form.is_valid(): if form.has_changed(): - latest_revision = question.get_latest_revision() - retagged_at = datetime.datetime.now() - # Update the Question itself - Question.objects.filter(id=question.id).update( - tagnames = form.cleaned_data['tags'], - last_edited_at = retagged_at, - last_edited_by = request.user, - last_activity_at = retagged_at, - last_activity_by = request.user - ) - # Update the Question's tag associations - tags_updated = Question.objects.update_tags(question, - form.cleaned_data['tags'], request.user) - # Create a new revision - QuestionRevision.objects.create( - question = question, - title = latest_revision.title, - author = request.user, - revised_at = retagged_at, - tagnames = form.cleaned_data['tags'], - summary = CONST['retagged'], - text = latest_revision.text + question.retag( + retagged_by = request.user, + retagged_at = datetime.datetime.now(), + tagnames = form.cleaned_data['tags'], ) - # send tags updated singal - tags_updated.send(sender=question.__class__, question=question) - return HttpResponseRedirect(question.get_absolute_url()) else: form = RetagQuestionForm(question) @@ -201,7 +180,7 @@ def _edit_question(request, question):#non-url subview of edit_question - just e latest_revision = question.get_latest_revision() revision_form = None if request.method == 'POST': - if 'select_revision' in request.POST: + if 'select_revision' in request.POST:#revert-type edit # user has changed revistion number revision_form = RevisionForm(question, latest_revision, request.POST) if revision_form.is_valid(): @@ -211,60 +190,26 @@ def _edit_question(request, question):#non-url subview of edit_question - just e revision=revision_form.cleaned_data['revision'])) else: form = EditQuestionForm(question, latest_revision, request.POST) - else: + else:#new content edit # Always check modifications against the latest revision form = EditQuestionForm(question, latest_revision, request.POST) if form.is_valid(): - html = sanitize_html(markdowner.convert(form.cleaned_data['text'])) if form.has_changed(): edited_at = datetime.datetime.now() - tags_changed = (latest_revision.tagnames != - form.cleaned_data['tags']) - tags_updated = False - # Update the Question itself - updated_fields = { - 'title': form.cleaned_data['title'], - 'last_edited_at': edited_at, - 'last_edited_by': request.user, - 'last_activity_at': edited_at, - 'last_activity_by': request.user, - 'tagnames': form.cleaned_data['tags'], - 'summary': strip_tags(html)[:120], - 'html': html, - } - - # only save when it's checked - # because wiki doesn't allow to be edited if last version has been enabled already - # and we make sure this in forms. - if ('wiki' in form.cleaned_data and - form.cleaned_data['wiki']): - updated_fields['wiki'] = True - updated_fields['wikified_at'] = edited_at - - Question.objects.filter( - id=question.id).update(**updated_fields) - # Update the Question's tag associations - if tags_changed: - tags_updated = Question.objects.update_tags( - question, form.cleaned_data['tags'], request.user) - # Create a new revision - revision = QuestionRevision( - question = question, - title = form.cleaned_data['title'], - author = request.user, - revised_at = edited_at, - tagnames = form.cleaned_data['tags'], - text = form.cleaned_data['text'], + edited_by = request.user + question.apply_edit( + edited_at = edited_at, + edited_by = edited_by, + title = form.cleaned_data['title'], + text = form.cleaned_data['text'], + #todo: summary name clash in question and question revision + comment = form.cleaned_data['summary'], + tags = form.cleaned_data['tags'], + wiki = form.cleaned_data.get('wiki',False), ) - if form.cleaned_data['summary']: - revision.summary = form.cleaned_data['summary'] - else: - revision.summary = 'No.%s Revision' % latest_revision.revision - revision.save() return HttpResponseRedirect(question.get_absolute_url()) else: - revision_form = RevisionForm(question, latest_revision) form = EditQuestionForm(question, latest_revision) return render_to_response('question_edit.html', { @@ -297,33 +242,15 @@ def edit_answer(request, id): else: form = EditAnswerForm(answer, latest_revision, request.POST) if form.is_valid(): - html = sanitize_html(markdowner.convert(form.cleaned_data['text'])) if form.has_changed(): edited_at = datetime.datetime.now() - updated_fields = { - 'last_edited_at': edited_at, - 'last_edited_by': request.user, - 'html': html, - } - Answer.objects.filter(id=answer.id).update(**updated_fields) - - revision = AnswerRevision( - answer=answer, - author=request.user, - revised_at=edited_at, - text=form.cleaned_data['text'] - ) - - if form.cleaned_data['summary']: - revision.summary = form.cleaned_data['summary'] - else: - revision.summary = 'No.%s Revision' % latest_revision.revision - revision.save() - - answer.question.last_activity_at = edited_at - answer.question.last_activity_by = request.user - answer.question.save() - + answer.apply_edit( + edited_at = edited_at, + edited_by = request.user, + text = form.cleaned_data['text'], + comment = form.cleaned_data['summary'], + wiki = False,#todo: fix this there is no "wiki" field on "edit answer" + ) return HttpResponseRedirect(answer.get_absolute_url()) else: revision_form = RevisionForm(answer, latest_revision) @@ -354,18 +281,16 @@ def answer(request, id):#process a new answer ) else: request.session.flush() - html = sanitize_html(markdowner.convert(text)) - summary = strip_tags(html)[:120] anon = AnonymousAnswer( question=question, wiki=wiki, text=text, - summary=summary, + summary=strip_tags(text)[:120], session_key=request.session.session_key, ip_addr=request.META['REMOTE_ADDR'], ) anon.save() - return HttpResponseRedirect(reverse('user_signin_new_answer')) + return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newanswer'})) return HttpResponseRedirect(question.get_absolute_url()) @@ -412,11 +337,10 @@ def __comments(request, obj, type):#non-view generic ajax handler to load commen response = __generate_comments_json(obj, type, user) elif request.method == "POST": if auth.can_add_comments(user,obj): - comment_data = request.POST.get('comment') - comment = Comment(content_object=obj, comment=comment_data, user=request.user) - comment.save() - obj.comment_count = obj.comment_count + 1 - obj.save() + obj.add_comment( + comment = request.POST.get('comment'), + user = request.user, + ) response = __generate_comments_json(obj, type, user) else: response = HttpResponseForbidden(mimetype="application/json") diff --git a/forum_modules/facebookauth/__init__.py b/forum_modules/facebookauth/__init__.py new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/forum_modules/facebookauth/__init__.py diff --git a/forum_modules/facebookauth/authentication.py b/forum_modules/facebookauth/authentication.py new file mode 100755 index 00000000..512367a3 --- /dev/null +++ b/forum_modules/facebookauth/authentication.py @@ -0,0 +1,85 @@ +import hashlib +from time import time +from datetime import datetime +from urllib import urlopen, urlencode +from forum.authentication.base import AuthenticationConsumer, ConsumerTemplateContext, InvalidAuthentication +from django.utils.translation import ugettext as _ + +import settings + +try: + from json import load as load_json +except: + from django.utils.simplejson import JSONDecoder + + def load_json(json): + decoder = JSONDecoder() + return decoder.decode(json.read()) + +class FacebookAuthConsumer(AuthenticationConsumer): + + def process_authentication_request(self, request): + API_KEY = settings.FB_API_KEY + + if API_KEY in request.COOKIES: + if self.check_cookies_signature(request.COOKIES): + if self.check_session_expiry(request.COOKIES): + return request.COOKIES[API_KEY + '_user'] + else: + raise InvalidAuthentication(_('Sorry, your Facebook session has expired, please try again')) + else: + raise InvalidAuthentication(_('The authentication with Facebook connect failed due to an invalid signature')) + else: + raise InvalidAuthentication(_('The authentication with Facebook connect failed, cannot find authentication tokens')) + + def generate_signature(self, values): + keys = [] + + for key in sorted(values.keys()): + keys.append(key) + + signature = ''.join(['%s=%s' % (key, values[key]) for key in keys]) + settings.FB_APP_SECRET + return hashlib.md5(signature).hexdigest() + + def check_session_expiry(self, cookies): + return datetime.fromtimestamp(float(cookies[settings.FB_API_KEY+'_expires'])) > datetime.now() + + def check_cookies_signature(self, cookies): + API_KEY = settings.FB_API_KEY + + values = {} + + for key in cookies.keys(): + if (key.startswith(API_KEY + '_')): + values[key.replace(API_KEY + '_', '')] = cookies[key] + + return self.generate_signature(values) == cookies[API_KEY] + + def get_user_data(self, key): + request_data = { + 'method': 'Users.getInfo', + 'api_key': settings.FB_API_KEY, + 'call_id': time(), + 'v': '1.0', + 'uids': key, + 'fields': 'name,first_name,last_name,email', + 'format': 'json', + } + + request_data['sig'] = self.generate_signature(request_data) + fb_response = load_json(urlopen(settings.REST_SERVER, urlencode(request_data)))[0] + + return { + 'username': fb_response['first_name'] + ' ' + fb_response['last_name'], + 'email': fb_response['email'] + } + +class FacebookAuthContext(ConsumerTemplateContext): + mode = 'BIGICON' + type = 'CUSTOM' + weight = 100 + human_name = 'Facebook' + code_template = 'modules/facebookauth/button.html' + extra_css = ["http://www.facebook.com/css/connect/connect_button.css"] + + API_KEY = settings.FB_API_KEY
\ No newline at end of file diff --git a/forum_modules/facebookauth/settings.py b/forum_modules/facebookauth/settings.py new file mode 100755 index 00000000..67bf80c1 --- /dev/null +++ b/forum_modules/facebookauth/settings.py @@ -0,0 +1,3 @@ +REST_SERVER = 'http://api.facebook.com/restserver.php' +FB_API_KEY = 'f773fab7be12aea689948208f37ad336' +FB_APP_SECRET = '894547c1b8db54d77f919b1695ae879c'
\ No newline at end of file diff --git a/forum_modules/facebookauth/templates/button.html b/forum_modules/facebookauth/templates/button.html new file mode 100755 index 00000000..ceae1fc2 --- /dev/null +++ b/forum_modules/facebookauth/templates/button.html @@ -0,0 +1,38 @@ +<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/en_US" type="text/javascript"></script>
+<script type="text/javascript">
+ var FB_API_KEY = "{{ provider.API_KEY }}";
+ var FB_CHANNEL_PATH = "{% url xd_receiver %}";
+
+ FB.init(FB_API_KEY, FB_CHANNEL_PATH, {permsToRequestOnConnect : "email"});
+
+ function FB_ConnectPostAuthorization() {
+ if ($('#validate_email').attr('checked')) {
+ FB_RequireFeatures(["Api"], function(){
+ var api = FB.Facebook.apiClient;
+ var fb_uid = api.get_session().uid;
+
+ $.post('{% url facebook_user_is_registered %}', {'fb_uid': fb_uid}, function(response) {
+ if (response != "yes") {
+ api.users_hasAppPermission("email", function(result) {
+ if (!result) {
+ FB.Connect.showPermissionDialog("email", redirect_to_done_page);
+ } else {
+ redirect_to_done_page()
+ }
+ })
+ } else {
+ redirect_to_done_page()
+ }
+ });
+ });
+ } else {
+ redirect_to_done_page();
+ }
+ }
+
+ function redirect_to_done_page() {
+ window.location = "{% url auth_provider_done provider=provider.id %}";
+ }
+
+</script>
+<fb:login-button v="2" size="medium" onlogin="FB_ConnectPostAuthorization()">Facebook</fb:login-button>
\ No newline at end of file diff --git a/forum_modules/facebookauth/templates/xd_receiver.html b/forum_modules/facebookauth/templates/xd_receiver.html new file mode 100755 index 00000000..9c1664d2 --- /dev/null +++ b/forum_modules/facebookauth/templates/xd_receiver.html @@ -0,0 +1 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <body> <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.js" type="text/javascript"></script> </body> </html>
diff --git a/forum_modules/facebookauth/urls.py b/forum_modules/facebookauth/urls.py new file mode 100755 index 00000000..cbe3b6c7 --- /dev/null +++ b/forum_modules/facebookauth/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import * +from django.views.generic.simple import direct_to_template + +from views import user_is_registered + +urlpatterns = patterns('', + url(r'^xd_receiver.htm$', direct_to_template, {'template': 'modules/facebookauth/xd_receiver.html'}, name='xd_receiver'), + url(r'^facebook/user_is_registered/', user_is_registered, name="facebook_user_is_registered"), +)
\ No newline at end of file diff --git a/forum_modules/facebookauth/views.py b/forum_modules/facebookauth/views.py new file mode 100755 index 00000000..f77c6282 --- /dev/null +++ b/forum_modules/facebookauth/views.py @@ -0,0 +1,11 @@ +from forum.models import AuthKeyUserAssociation +from django.http import HttpResponse + +def user_is_registered(request): + try: + fb_uid = request.POST['fb_uid'] + #print fb_uid + AuthKeyUserAssociation.objects.get(key=fb_uid) + return HttpResponse('yes') + except: + return HttpResponse('no')
\ No newline at end of file diff --git a/forum_modules/localauth/__init__.py b/forum_modules/localauth/__init__.py new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/forum_modules/localauth/__init__.py diff --git a/forum_modules/localauth/authentication.py b/forum_modules/localauth/authentication.py new file mode 100755 index 00000000..770ea08f --- /dev/null +++ b/forum_modules/localauth/authentication.py @@ -0,0 +1,18 @@ +from forum.authentication.base import AuthenticationConsumer, ConsumerTemplateContext, InvalidAuthentication +from forms import ClassicLoginForm + +class LocalAuthConsumer(AuthenticationConsumer): + def process_authentication_request(self, request): + form_auth = ClassicLoginForm(request.POST) + + if form_auth.is_valid(): + return form_auth.get_user() + else: + raise InvalidAuthentication(" ".join(form_auth.errors.values()[0])) + +class LocalAuthContext(ConsumerTemplateContext): + mode = 'STACK_ITEM' + weight = 1000 + human_name = 'Local authentication' + stack_item_template = 'modules/localauth/loginform.html' + show_to_logged_in_user = False
\ No newline at end of file diff --git a/forum_modules/localauth/forms.py b/forum_modules/localauth/forms.py new file mode 100755 index 00000000..8afa2b05 --- /dev/null +++ b/forum_modules/localauth/forms.py @@ -0,0 +1,77 @@ +from forum.utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm +from forum.models import EmailFeedSetting, Question +from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext as _ +from django.contrib.auth import authenticate +from django import forms +import logging + +class ClassicRegisterForm(SetPasswordForm): + """ legacy registration form """ + + next = NextUrlField() + username = UserNameField() + email = UserEmailField() + #fields password1 and password2 are inherited + #recaptcha = ReCaptchaField() + +class ClassicLoginForm(forms.Form): + """ legacy account signin form """ + next = NextUrlField() + username = UserNameField(required=False,skip_clean=True) + password = forms.CharField(max_length=128, + widget=forms.widgets.PasswordInput(attrs={'class':'required login'}), + required=False) + + def __init__(self, data=None, files=None, auto_id='id_%s', + prefix=None, initial=None): + super(ClassicLoginForm, self).__init__(data, files, auto_id, + prefix, initial) + self.user_cache = None + + def _clean_nonempty_field(self,field): + value = None + if field in self.cleaned_data: + value = str(self.cleaned_data[field]).strip() + if value == '': + value = None + self.cleaned_data[field] = value + return value + + def clean_username(self): + return self._clean_nonempty_field('username') + + def clean_password(self): + return self._clean_nonempty_field('password') + + def clean(self): + error_list = [] + username = self.cleaned_data['username'] + password = self.cleaned_data['password'] + + self.user_cache = None + if username and password: + self.user_cache = authenticate(username=username, password=password) + + if self.user_cache is None: + del self.cleaned_data['username'] + del self.cleaned_data['password'] + error_list.insert(0,(_("Please enter valid username and password " + "(both are case-sensitive)."))) + elif self.user_cache.is_active == False: + error_list.append(_("This account is inactive.")) + if len(error_list) > 0: + error_list.insert(0,_('Login failed.')) + elif password == None and username == None: + error_list.append(_('Please enter username and password')) + elif password == None: + error_list.append(_('Please enter your password')) + elif username == None: + error_list.append(_('Please enter user name')) + if len(error_list) > 0: + self._errors['__all__'] = forms.util.ErrorList(error_list) + return self.cleaned_data + + def get_user(self): + """ get authenticated user """ + return self.user_cache
\ No newline at end of file diff --git a/forum_modules/localauth/templates/loginform.html b/forum_modules/localauth/templates/loginform.html new file mode 100755 index 00000000..b1784fb6 --- /dev/null +++ b/forum_modules/localauth/templates/loginform.html @@ -0,0 +1,31 @@ +{% load i18n %}
+
+<fieldset id='local_login_fs'>
+ <p><span class='big strong'>Enter your local user name and password</span><br/><span class='grey'>(or select your external provider above)</span></p>
+ <table>
+ <tr>
+ <td>
+ <label for="id_username">Login name</label>
+ </td>
+ <td>
+ <input id="id_username" type="text" class="required login" name="username" maxlength="30" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label for="id_password">Password</label>
+ </td>
+ <td>
+ <input id="id_password" type="password" class="required login" name="password" maxlength="128" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input id="blogin" name="blogin" type="submit" value="Login" />
+ </td>
+ <td>
+ <a href="{% url auth_local_register %}">Create account</a><span> | </span><a href="{% url auth_request_tempsignin %}">Forgot your password?</a>
+ </td>
+ </tr>
+ </table>
+</fieldset>
\ No newline at end of file diff --git a/forum_modules/localauth/urls.py b/forum_modules/localauth/urls.py new file mode 100755 index 00000000..aeebc40a --- /dev/null +++ b/forum_modules/localauth/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import * +from django.views.generic.simple import direct_to_template +from django.utils.translation import ugettext as _ +import views as app + +urlpatterns = patterns('', + url(r'^%s%s%s$' % (_('account/'), _('local/'), _('register/')), app.register, name='auth_local_register'), +)
\ No newline at end of file diff --git a/forum_modules/localauth/views.py b/forum_modules/localauth/views.py new file mode 100755 index 00000000..db71e902 --- /dev/null +++ b/forum_modules/localauth/views.py @@ -0,0 +1,31 @@ +from django.contrib.auth.models import User +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.utils.translation import ugettext as _ + +from forms import ClassicRegisterForm +from forum.authentication.forms import SimpleEmailSubscribeForm +from forum.views.auth import login_and_forward, send_validation_email + +def register(request): + if request.method == 'POST': + form = ClassicRegisterForm(request.POST) + email_feeds_form = SimpleEmailSubscribeForm(request.POST) + + if form.is_valid() and email_feeds_form.is_valid(): + username = form.cleaned_data['username'] + password = form.cleaned_data['password1'] + email = form.cleaned_data['email'] + + user_ = User.objects.create_user( username,email,password ) + send_validation_email(user_) + email_feeds_form.save(user_) + return login_and_forward(request, user_, None, _("A validation email has been sent to your email address. ")) + else: + form = ClassicRegisterForm(initial={'next':'/'}) + email_feeds_form = SimpleEmailSubscribeForm() + + return render_to_response('auth/signup.html', { + 'form': form, + 'email_feeds_form': email_feeds_form + }, context_instance=RequestContext(request))
\ No newline at end of file diff --git a/forum_modules/oauthauth/__init__.py b/forum_modules/oauthauth/__init__.py new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/forum_modules/oauthauth/__init__.py diff --git a/forum_modules/oauthauth/authentication.py b/forum_modules/oauthauth/authentication.py new file mode 100755 index 00000000..04145461 --- /dev/null +++ b/forum_modules/oauthauth/authentication.py @@ -0,0 +1,41 @@ +from consumer import OAuthAbstractAuthConsumer +from forum.authentication.base import ConsumerTemplateContext + +try: + import json as simplejson +except ImportError: + from django.utils import simplejson + +from lib import oauth +import settings + +class TwitterAuthConsumer(OAuthAbstractAuthConsumer): + def __init__(self): + OAuthAbstractAuthConsumer.__init__(self, + settings.TWITTER_CONSUMER_KEY, + settings.TWITTER_CONSUMER_SECRET, + "twitter.com", + "https://twitter.com/oauth/request_token", + "https://twitter.com/oauth/access_token", + "https://twitter.com/oauth/authorize", + ) + + def get_user_data(self, key): + json = self.fetch_data(key, "https://twitter.com/account/verify_credentials.json") + + if 'screen_name' in json: + creds = simplejson.loads(json) + + return { + 'username': creds['screen_name'] + } + + + return {} + +class TwitterAuthContext(ConsumerTemplateContext): + mode = 'BIGICON' + type = 'DIRECT' + weight = 150 + human_name = 'Twitter' + icon = '/media/images/openid/twitter.png'
\ No newline at end of file diff --git a/forum_modules/oauthauth/consumer.py b/forum_modules/oauthauth/consumer.py new file mode 100755 index 00000000..74734145 --- /dev/null +++ b/forum_modules/oauthauth/consumer.py @@ -0,0 +1,87 @@ +import urllib +import urllib2 +import httplib +import time + +from forum.authentication.base import AuthenticationConsumer, InvalidAuthentication +from django.utils.translation import ugettext as _ + +from lib import oauth + +class OAuthAbstractAuthConsumer(AuthenticationConsumer): + + def __init__(self, consumer_key, consumer_secret, server_url, request_token_url, access_token_url, authorization_url): + self.consumer_secret = consumer_secret + self.consumer_key = consumer_key + + self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret) + self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1() + + self.server_url = server_url + self.request_token_url = request_token_url + self.access_token_url = access_token_url + self.authorization_url = authorization_url + + def prepare_authentication_request(self, request, redirect_to): + request_token = self.fetch_request_token() + request.session['unauthed_token'] = request_token.to_string() + return self.authorize_token_url(request_token) + + def process_authentication_request(self, request): + unauthed_token = request.session.get('unauthed_token', None) + if not unauthed_token: + raise InvalidAuthentication(_('Error, the oauth token is not on the server')) + + token = oauth.OAuthToken.from_string(unauthed_token) + + if token.key != request.GET.get('oauth_token', 'no-token'): + raise InvalidAuthentication(_("Something went wrong! Auth tokens do not match")) + + access_token = self.fetch_access_token(token) + + return access_token.to_string() + + def get_user_data(self, key): + #token = oauth.OAuthToken.from_string(access_token) + return {} + + def fetch_request_token(self): + oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, http_url=self.request_token_url) + oauth_request.sign_request(self.signature_method, self.consumer, None) + params = oauth_request.parameters + data = urllib.urlencode(params) + full_url='%s?%s'%(self.request_token_url, data) + response = urllib2.urlopen(full_url) + return oauth.OAuthToken.from_string(response.read()) + + def authorize_token_url(self, token, callback_url=None): + oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token,\ + callback=callback_url, http_url=self.authorization_url) + params = oauth_request.parameters + data = urllib.urlencode(params) + full_url='%s?%s'%(self.authorization_url, data) + return full_url + + def fetch_access_token(self, token): + oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, http_url=self.access_token_url) + oauth_request.sign_request(self.signature_method, self.consumer, token) + params = oauth_request.parameters + data = urllib.urlencode(params) + full_url='%s?%s'%(self.access_token_url, data) + response = urllib2.urlopen(full_url) + return oauth.OAuthToken.from_string(response.read()) + + def fetch_data(self, token, http_url, parameters=None): + access_token = oauth.OAuthToken.from_string(token) + oauth_request = oauth.OAuthRequest.from_consumer_and_token( + self.consumer, token=access_token, http_method="GET", + http_url=http_url, parameters=parameters, + ) + oauth_request.sign_request(self.signature_method, self.consumer, access_token) + + url = oauth_request.to_url() + connection = httplib.HTTPSConnection(self.server_url) + connection.request(oauth_request.http_method, url) + + return connection.getresponse().read() + diff --git a/forum_modules/oauthauth/lib/__init__.py b/forum_modules/oauthauth/lib/__init__.py new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/forum_modules/oauthauth/lib/__init__.py diff --git a/forum_modules/oauthauth/lib/oauth.py b/forum_modules/oauthauth/lib/oauth.py new file mode 100755 index 00000000..89abf858 --- /dev/null +++ b/forum_modules/oauthauth/lib/oauth.py @@ -0,0 +1,594 @@ +""" +The MIT License + +Copyright (c) 2007 Leah Culver + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import cgi +import urllib +import time +import random +import urlparse +import hmac +import binascii + + +VERSION = '1.0' # Hi Blaine! +HTTP_METHOD = 'GET' +SIGNATURE_METHOD = 'PLAINTEXT' + + +class OAuthError(RuntimeError): + """Generic exception class.""" + def __init__(self, message='OAuth error occured.'): + self.message = message + +def build_authenticate_header(realm=''): + """Optional WWW-Authenticate header (401 error)""" + return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} + +def escape(s): + """Escape a URL including any /.""" + return urllib.quote(s, safe='~') + +def _utf8_str(s): + """Convert unicode to utf-8.""" + if isinstance(s, unicode): + return s.encode("utf-8") + else: + return str(s) + +def generate_timestamp(): + """Get seconds since epoch (UTC).""" + return int(time.time()) + +def generate_nonce(length=8): + """Generate pseudorandom number.""" + return ''.join([str(random.randint(0, 9)) for i in range(length)]) + + +class OAuthConsumer(object): + """Consumer of OAuth authentication. + + OAuthConsumer is a data type that represents the identity of the Consumer + via its shared secret with the Service Provider. + + """ + key = None + secret = None + + def __init__(self, key, secret): + self.key = key + self.secret = secret + + +class OAuthToken(object): + """OAuthToken is a data type that represents an End User via either an access + or request token. + + key -- the token + secret -- the token secret + + """ + key = None + secret = None + + def __init__(self, key, secret): + self.key = key + self.secret = secret + + def to_string(self): + return urllib.urlencode({'oauth_token': self.key, + 'oauth_token_secret': self.secret}) + + def from_string(s): + """ Returns a token from something like: + oauth_token_secret=xxx&oauth_token=xxx + """ + params = cgi.parse_qs(s, keep_blank_values=False) + key = params['oauth_token'][0] + secret = params['oauth_token_secret'][0] + return OAuthToken(key, secret) + from_string = staticmethod(from_string) + + def __str__(self): + return self.to_string() + + +class OAuthRequest(object): + """OAuthRequest represents the request and can be serialized. + + OAuth parameters: + - oauth_consumer_key + - oauth_token + - oauth_signature_method + - oauth_signature + - oauth_timestamp + - oauth_nonce + - oauth_version + ... any additional parameters, as defined by the Service Provider. + """ + parameters = None # OAuth parameters. + http_method = HTTP_METHOD + http_url = None + version = VERSION + + def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None): + self.http_method = http_method + self.http_url = http_url + self.parameters = parameters or {} + + def set_parameter(self, parameter, value): + self.parameters[parameter] = value + + def get_parameter(self, parameter): + try: + return self.parameters[parameter] + except: + raise OAuthError('Parameter not found: %s' % parameter) + + def _get_timestamp_nonce(self): + return self.get_parameter('oauth_timestamp'), self.get_parameter( + 'oauth_nonce') + + def get_nonoauth_parameters(self): + """Get any non-OAuth parameters.""" + parameters = {} + for k, v in self.parameters.iteritems(): + # Ignore oauth parameters. + if k.find('oauth_') < 0: + parameters[k] = v + return parameters + + def to_header(self, realm=''): + """Serialize as a header for an HTTPAuth request.""" + auth_header = 'OAuth realm="%s"' % realm + # Add the oauth parameters. + if self.parameters: + for k, v in self.parameters.iteritems(): + if k[:6] == 'oauth_': + auth_header += ', %s="%s"' % (k, escape(str(v))) + return {'Authorization': auth_header} + + def to_postdata(self): + """Serialize as post data for a POST request.""" + return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) \ + for k, v in self.parameters.iteritems()]) + + def to_url(self): + """Serialize as a URL for a GET request.""" + return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata()) + + def get_normalized_parameters(self): + """Return a string that contains the parameters that must be signed.""" + params = self.parameters + try: + # Exclude the signature if it exists. + del params['oauth_signature'] + except: + pass + # Escape key values before sorting. + key_values = [(escape(_utf8_str(k)), escape(_utf8_str(v))) \ + for k,v in params.items()] + # Sort lexicographically, first after key, then after value. + key_values.sort() + # Combine key value pairs into a string. + return '&'.join(['%s=%s' % (k, v) for k, v in key_values]) + + def get_normalized_http_method(self): + """Uppercases the http method.""" + return self.http_method.upper() + + def get_normalized_http_url(self): + """Parses the URL and rebuilds it to be scheme://host/path.""" + parts = urlparse.urlparse(self.http_url) + scheme, netloc, path = parts[:3] + # Exclude default port numbers. + if scheme == 'http' and netloc[-3:] == ':80': + netloc = netloc[:-3] + elif scheme == 'https' and netloc[-4:] == ':443': + netloc = netloc[:-4] + return '%s://%s%s' % (scheme, netloc, path) + + def sign_request(self, signature_method, consumer, token): + """Set the signature parameter to the result of build_signature.""" + # Set the signature method. + self.set_parameter('oauth_signature_method', + signature_method.get_name()) + # Set the signature. + self.set_parameter('oauth_signature', + self.build_signature(signature_method, consumer, token)) + + def build_signature(self, signature_method, consumer, token): + """Calls the build signature method within the signature method.""" + return signature_method.build_signature(self, consumer, token) + + def from_request(http_method, http_url, headers=None, parameters=None, + query_string=None): + """Combines multiple parameter sources.""" + if parameters is None: + parameters = {} + + # Headers + if headers and 'Authorization' in headers: + auth_header = headers['Authorization'] + # Check that the authorization header is OAuth. + if auth_header.index('OAuth') > -1: + auth_header = auth_header.lstrip('OAuth ') + try: + # Get the parameters from the header. + header_params = OAuthRequest._split_header(auth_header) + parameters.update(header_params) + except: + raise OAuthError('Unable to parse OAuth parameters from ' + 'Authorization header.') + + # GET or POST query string. + if query_string: + query_params = OAuthRequest._split_url_string(query_string) + parameters.update(query_params) + + # URL parameters. + param_str = urlparse.urlparse(http_url)[4] # query + url_params = OAuthRequest._split_url_string(param_str) + parameters.update(url_params) + + if parameters: + return OAuthRequest(http_method, http_url, parameters) + + return None + from_request = staticmethod(from_request) + + def from_consumer_and_token(oauth_consumer, token=None, + http_method=HTTP_METHOD, http_url=None, parameters=None): + if not parameters: + parameters = {} + + defaults = { + 'oauth_consumer_key': oauth_consumer.key, + 'oauth_timestamp': generate_timestamp(), + 'oauth_nonce': generate_nonce(), + 'oauth_version': OAuthRequest.version, + } + + defaults.update(parameters) + parameters = defaults + + if token: + parameters['oauth_token'] = token.key + + return OAuthRequest(http_method, http_url, parameters) + from_consumer_and_token = staticmethod(from_consumer_and_token) + + def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, + http_url=None, parameters=None): + if not parameters: + parameters = {} + + parameters['oauth_token'] = token.key + + if callback: + parameters['oauth_callback'] = callback + + return OAuthRequest(http_method, http_url, parameters) + from_token_and_callback = staticmethod(from_token_and_callback) + + def _split_header(header): + """Turn Authorization: header into parameters.""" + params = {} + parts = header.split(',') + for param in parts: + # Ignore realm parameter. + if param.find('realm') > -1: + continue + # Remove whitespace. + param = param.strip() + # Split key-value. + param_parts = param.split('=', 1) + # Remove quotes and unescape the value. + params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) + return params + _split_header = staticmethod(_split_header) + + def _split_url_string(param_str): + """Turn URL string into parameters.""" + parameters = cgi.parse_qs(param_str, keep_blank_values=False) + for k, v in parameters.iteritems(): + parameters[k] = urllib.unquote(v[0]) + return parameters + _split_url_string = staticmethod(_split_url_string) + +class OAuthServer(object): + """A worker to check the validity of a request against a data store.""" + timestamp_threshold = 300 # In seconds, five minutes. + version = VERSION + signature_methods = None + data_store = None + + def __init__(self, data_store=None, signature_methods=None): + self.data_store = data_store + self.signature_methods = signature_methods or {} + + def set_data_store(self, data_store): + self.data_store = data_store + + def get_data_store(self): + return self.data_store + + def add_signature_method(self, signature_method): + self.signature_methods[signature_method.get_name()] = signature_method + return self.signature_methods + + def fetch_request_token(self, oauth_request): + """Processes a request_token request and returns the + request token on success. + """ + try: + # Get the request token for authorization. + token = self._get_token(oauth_request, 'request') + except OAuthError: + # No token required for the initial token request. + version = self._get_version(oauth_request) + consumer = self._get_consumer(oauth_request) + self._check_signature(oauth_request, consumer, None) + # Fetch a new token. + token = self.data_store.fetch_request_token(consumer) + return token + + def fetch_access_token(self, oauth_request): + """Processes an access_token request and returns the + access token on success. + """ + version = self._get_version(oauth_request) + consumer = self._get_consumer(oauth_request) + # Get the request token. + token = self._get_token(oauth_request, 'request') + self._check_signature(oauth_request, consumer, token) + new_token = self.data_store.fetch_access_token(consumer, token) + return new_token + + def verify_request(self, oauth_request): + """Verifies an api call and checks all the parameters.""" + # -> consumer and token + version = self._get_version(oauth_request) + consumer = self._get_consumer(oauth_request) + # Get the access token. + token = self._get_token(oauth_request, 'access') + self._check_signature(oauth_request, consumer, token) + parameters = oauth_request.get_nonoauth_parameters() + return consumer, token, parameters + + def authorize_token(self, token, user): + """Authorize a request token.""" + return self.data_store.authorize_request_token(token, user) + + def get_callback(self, oauth_request): + """Get the callback URL.""" + return oauth_request.get_parameter('oauth_callback') + + def build_authenticate_header(self, realm=''): + """Optional support for the authenticate header.""" + return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} + + def _get_version(self, oauth_request): + """Verify the correct version request for this server.""" + try: + version = oauth_request.get_parameter('oauth_version') + except: + version = VERSION + if version and version != self.version: + raise OAuthError('OAuth version %s not supported.' % str(version)) + return version + + def _get_signature_method(self, oauth_request): + """Figure out the signature with some defaults.""" + try: + signature_method = oauth_request.get_parameter( + 'oauth_signature_method') + except: + signature_method = SIGNATURE_METHOD + try: + # Get the signature method object. + signature_method = self.signature_methods[signature_method] + except: + signature_method_names = ', '.join(self.signature_methods.keys()) + raise OAuthError('Signature method %s not supported try one of the ' + 'following: %s' % (signature_method, signature_method_names)) + + return signature_method + + def _get_consumer(self, oauth_request): + consumer_key = oauth_request.get_parameter('oauth_consumer_key') + consumer = self.data_store.lookup_consumer(consumer_key) + if not consumer: + raise OAuthError('Invalid consumer.') + return consumer + + def _get_token(self, oauth_request, token_type='access'): + """Try to find the token for the provided request token key.""" + token_field = oauth_request.get_parameter('oauth_token') + token = self.data_store.lookup_token(token_type, token_field) + if not token: + raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) + return token + + def _check_signature(self, oauth_request, consumer, token): + timestamp, nonce = oauth_request._get_timestamp_nonce() + self._check_timestamp(timestamp) + self._check_nonce(consumer, token, nonce) + signature_method = self._get_signature_method(oauth_request) + try: + signature = oauth_request.get_parameter('oauth_signature') + except: + raise OAuthError('Missing signature.') + # Validate the signature. + valid_sig = signature_method.check_signature(oauth_request, consumer, + token, signature) + if not valid_sig: + key, base = signature_method.build_signature_base_string( + oauth_request, consumer, token) + raise OAuthError('Invalid signature. Expected signature base ' + 'string: %s' % base) + built = signature_method.build_signature(oauth_request, consumer, token) + + def _check_timestamp(self, timestamp): + """Verify that timestamp is recentish.""" + timestamp = int(timestamp) + now = int(time.time()) + lapsed = now - timestamp + if lapsed > self.timestamp_threshold: + raise OAuthError('Expired timestamp: given %d and now %s has a ' + 'greater difference than threshold %d' % + (timestamp, now, self.timestamp_threshold)) + + def _check_nonce(self, consumer, token, nonce): + """Verify that the nonce is uniqueish.""" + nonce = self.data_store.lookup_nonce(consumer, token, nonce) + if nonce: + raise OAuthError('Nonce already used: %s' % str(nonce)) + + +class OAuthClient(object): + """OAuthClient is a worker to attempt to execute a request.""" + consumer = None + token = None + + def __init__(self, oauth_consumer, oauth_token): + self.consumer = oauth_consumer + self.token = oauth_token + + def get_consumer(self): + return self.consumer + + def get_token(self): + return self.token + + def fetch_request_token(self, oauth_request): + """-> OAuthToken.""" + raise NotImplementedError + + def fetch_access_token(self, oauth_request): + """-> OAuthToken.""" + raise NotImplementedError + + def access_resource(self, oauth_request): + """-> Some protected resource.""" + raise NotImplementedError + + +class OAuthDataStore(object): + """A database abstraction used to lookup consumers and tokens.""" + + def lookup_consumer(self, key): + """-> OAuthConsumer.""" + raise NotImplementedError + + def lookup_token(self, oauth_consumer, token_type, token_token): + """-> OAuthToken.""" + raise NotImplementedError + + def lookup_nonce(self, oauth_consumer, oauth_token, nonce): + """-> OAuthToken.""" + raise NotImplementedError + + def fetch_request_token(self, oauth_consumer): + """-> OAuthToken.""" + raise NotImplementedError + + def fetch_access_token(self, oauth_consumer, oauth_token): + """-> OAuthToken.""" + raise NotImplementedError + + def authorize_request_token(self, oauth_token, user): + """-> OAuthToken.""" + raise NotImplementedError + + +class OAuthSignatureMethod(object): + """A strategy class that implements a signature method.""" + def get_name(self): + """-> str.""" + raise NotImplementedError + + def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token): + """-> str key, str raw.""" + raise NotImplementedError + + def build_signature(self, oauth_request, oauth_consumer, oauth_token): + """-> str.""" + raise NotImplementedError + + def check_signature(self, oauth_request, consumer, token, signature): + built = self.build_signature(oauth_request, consumer, token) + return built == signature + + +class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): + + def get_name(self): + return 'HMAC-SHA1' + + def build_signature_base_string(self, oauth_request, consumer, token): + sig = ( + escape(oauth_request.get_normalized_http_method()), + escape(oauth_request.get_normalized_http_url()), + escape(oauth_request.get_normalized_parameters()), + ) + + key = '%s&' % escape(consumer.secret) + if token: + key += escape(token.secret) + raw = '&'.join(sig) + return key, raw + + def build_signature(self, oauth_request, consumer, token): + """Builds the base signature string.""" + key, raw = self.build_signature_base_string(oauth_request, consumer, + token) + + # HMAC object. + try: + import hashlib # 2.5 + hashed = hmac.new(key, raw, hashlib.sha1) + except: + import sha # Deprecated + hashed = hmac.new(key, raw, sha) + + # Calculate the digest base 64. + return binascii.b2a_base64(hashed.digest())[:-1] + + +class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod): + + def get_name(self): + return 'PLAINTEXT' + + def build_signature_base_string(self, oauth_request, consumer, token): + """Concatenates the consumer key and secret.""" + sig = '%s&' % escape(consumer.secret) + if token: + sig = sig + escape(token.secret) + return sig, sig + + def build_signature(self, oauth_request, consumer, token): + key, raw = self.build_signature_base_string(oauth_request, consumer, + token) + return key
\ No newline at end of file diff --git a/forum_modules/oauthauth/settings.py b/forum_modules/oauthauth/settings.py new file mode 100755 index 00000000..67567b63 --- /dev/null +++ b/forum_modules/oauthauth/settings.py @@ -0,0 +1,3 @@ +TWITTER_CONSUMER_KEY = "sAAGwWILliIbgbrG37GztQ" +TWITTER_CONSUMER_SECRET = "AZv0pHTZQaf4rxxZOrj3Jm1RKgmlV4MnYJAsrY7M0" + diff --git a/forum_modules/openidauth/__init__.py b/forum_modules/openidauth/__init__.py new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/forum_modules/openidauth/__init__.py diff --git a/forum_modules/openidauth/authentication.py b/forum_modules/openidauth/authentication.py new file mode 100755 index 00000000..c04c44b9 --- /dev/null +++ b/forum_modules/openidauth/authentication.py @@ -0,0 +1,196 @@ +from consumer import OpenIdAbstractAuthConsumer +from forum.authentication.base import ConsumerTemplateContext + +class GoogleAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + return 'https://www.google.com/accounts/o8/id' + +class GoogleAuthContext(ConsumerTemplateContext): + mode = 'BIGICON' + type = 'DIRECT' + weight = 200 + human_name = 'Google' + icon = '/media/images/openid/google.gif' + + + +class YahooAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + return 'http://yahoo.com/' + +class YahooAuthContext(ConsumerTemplateContext): + mode = 'BIGICON' + type = 'DIRECT' + weight = 300 + human_name = 'Yahoo' + icon = '/media/images/openid/yahoo.gif' + + + +class AolAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + uname = request.POST['input_field'] + return 'http://openid.aol.com/' + uname + +class AolAuthContext(ConsumerTemplateContext): + mode = 'BIGICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'AOL screen name' + } + weight = 400 + human_name = 'AOL' + icon = '/media/images/openid/aol.gif' + + +class MyOpenIdAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + blog_name = request.POST['input_field'] + return "http://%s.myopenid.com/" % blog_name + +class MyOpenIdAuthContext(ConsumerTemplateContext): + mode = 'SMALLICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'MyOpenID user name' + } + weight = 200 + human_name = 'MyOpenID' + icon = '/media/images/openid/myopenid.ico' + + +class FlickrAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + blog_name = request.POST['input_field'] + return "http://flickr.com/%s/" % blog_name + +class FlickrAuthContext(ConsumerTemplateContext): + mode = 'SMALLICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'Flickr user name' + } + weight = 250 + human_name = 'Flickr' + icon = '/media/images/openid/flickr.ico' + + +class TechnoratiAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + blog_name = request.POST['input_field'] + return "http://technorati.com/people/technorati/%s/" % blog_name + +class TechnoratiAuthContext(ConsumerTemplateContext): + mode = 'SMALLICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'Technorati user name' + } + weight = 260 + human_name = 'Technorati' + icon = '/media/images/openid/technorati.ico' + + +class WordpressAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + blog_name = request.POST['input_field'] + return "http://%s.wordpress.com/" % blog_name + +class WordpressAuthContext(ConsumerTemplateContext): + mode = 'SMALLICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'Wordpress blog name' + } + weight = 270 + human_name = 'Wordpress' + icon = '/media/images/openid/wordpress.ico' + + +class BloggerAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + blog_name = request.POST['input_field'] + return "http://%s.blogspot.com/" % blog_name + +class BloggerAuthContext(ConsumerTemplateContext): + mode = 'SMALLICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'Blogger blog name' + } + weight = 300 + human_name = 'Blogger' + icon = '/media/images/openid/blogger.ico' + + +class LiveJournalAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + blog_name = request.POST['input_field'] + return "http://%s.livejournal.com/" % blog_name + +class LiveJournalAuthContext(ConsumerTemplateContext): + mode = 'SMALLICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'LiveJournal blog name' + } + weight = 310 + human_name = 'LiveJournal' + icon = '/media/images/openid/livejournal.ico' + + +class ClaimIdAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + blog_name = request.POST['input_field'] + return "http://claimid.com/%s" % blog_name + +class ClaimIdAuthContext(ConsumerTemplateContext): + mode = 'SMALLICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'ClaimID user name' + } + weight = 320 + human_name = 'ClaimID' + icon = '/media/images/openid/claimid.ico' + +class VidoopAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + blog_name = request.POST['input_field'] + return "http://%s.myvidoop.com/" % blog_name + +class VidoopAuthContext(ConsumerTemplateContext): + mode = 'SMALLICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'Vidoop user name' + } + weight = 330 + human_name = 'Vidoop' + icon = '/media/images/openid/vidoop.ico' + +class VerisignAuthConsumer(OpenIdAbstractAuthConsumer): + def get_user_url(self, request): + blog_name = request.POST['input_field'] + return "http://%s.pip.verisignlabs.com/" % blog_name + +class VerisignAuthContext(ConsumerTemplateContext): + mode = 'SMALLICON' + type = 'SIMPLE_FORM' + simple_form_context = { + 'your_what': 'Verisign user name' + } + weight = 340 + human_name = 'Verisign' + icon = '/media/images/openid/verisign.ico' + + +class OpenIdUrlAuthConsumer(OpenIdAbstractAuthConsumer): + pass + +class OpenIdUrlAuthContext(ConsumerTemplateContext): + mode = 'STACK_ITEM' + weight = 300 + human_name = 'OpenId url' + stack_item_template = 'modules/openidauth/openidurl.html' + icon = '/media/images/openid/openid-inputicon.gif'
\ No newline at end of file diff --git a/forum_modules/openidauth/consumer.py b/forum_modules/openidauth/consumer.py new file mode 100755 index 00000000..68035968 --- /dev/null +++ b/forum_modules/openidauth/consumer.py @@ -0,0 +1,112 @@ +from django.utils.html import escape +from django.http import get_host + +from forum.authentication.base import AuthenticationConsumer, InvalidAuthentication +import settings + +from openid.yadis import xri +from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE, SETUP_NEEDED +from openid.consumer.discover import DiscoveryFailure +from openid.extensions.sreg import SRegRequest, SRegResponse +from openid.extensions.ax import FetchRequest as AXFetchRequest, AttrInfo, FetchResponse as AXFetchResponse +from django.utils.translation import ugettext as _ + +from store import OsqaOpenIDStore + +class OpenIdAbstractAuthConsumer(AuthenticationConsumer): + + def get_user_url(self, request): + try: + return request.POST['openid_identifier'] + except: + raise NotImplementedError() + + def prepare_authentication_request(self, request, redirect_to): + if not redirect_to.startswith('http://') or redirect_to.startswith('https://'): + redirect_to = get_url_host(request) + redirect_to + + user_url = self.get_user_url(request) + + if xri.identifierScheme(user_url) == 'XRI' and getattr( + settings, 'OPENID_DISALLOW_INAMES', False + ): + raise InvalidAuthentication('i-names are not supported') + + consumer = Consumer(request.session, OsqaOpenIDStore()) + + try: + auth_request = consumer.begin(user_url) + except DiscoveryFailure: + raise InvalidAuthentication(_('Sorry, but your input is not a valid OpenId')) + + #sreg = getattr(settings, 'OPENID_SREG', False) + + #if sreg: + # s = SRegRequest() + # for sarg in sreg: + # if sarg.lower().lstrip() == "policy_url": + # s.policy_url = sreg[sarg] + # else: + # for v in sreg[sarg].split(','): + # s.requestField(field_name=v.lower().lstrip(), required=(sarg.lower().lstrip() == "required")) + # auth_request.addExtension(s) + + #auth_request.addExtension(SRegRequest(required=['email'])) + + if request.session.get('force_email_request', True): + axr = AXFetchRequest() + axr.add(AttrInfo("http://axschema.org/contact/email", 1, True, "email")) + auth_request.addExtension(axr) + + trust_root = getattr( + settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/' + ) + + + return auth_request.redirectURL(trust_root, redirect_to) + + def process_authentication_request(self, request): + consumer = Consumer(request.session, OsqaOpenIDStore()) + + query_dict = dict([ + (k.encode('utf8'), v.encode('utf8')) for k, v in request.GET.items() + ]) + + #for i in query_dict.items(): + # print "%s : %s" % i + + url = get_url_host(request) + request.path + openid_response = consumer.complete(query_dict, url) + + if openid_response.status == SUCCESS: + if request.session.get('force_email_request', True): + try: + ax = AXFetchResponse.fromSuccessResponse(openid_response) + email = ax.getExtensionArgs()['value.ext0.1'] + request.session['auth_email_request'] = email + except Exception, e: + pass + + return request.GET['openid.identity'] + elif openid_response.status == CANCEL: + raise InvalidAuthentication(_('The OpenId authentication request was canceled')) + elif openid_response.status == FAILURE: + raise InvalidAuthentication(_('The OpenId authentication failed: ') + openid_response.message) + elif openid_response.status == SETUP_NEEDED: + raise InvalidAuthentication(_('Setup needed')) + else: + raise InvalidAuthentication(_('The OpenId authentication failed with an unknown status: ') + openid_response.status) + + def get_user_data(self, key): + return {} + +def get_url_host(request): + if request.is_secure(): + protocol = 'https' + else: + protocol = 'http' + host = escape(get_host(request)) + return '%s://%s' % (protocol, host) + +def get_full_url(request): + return get_url_host(request) + request.get_full_path()
\ No newline at end of file diff --git a/forum_modules/openidauth/models.py b/forum_modules/openidauth/models.py new file mode 100755 index 00000000..d76902df --- /dev/null +++ b/forum_modules/openidauth/models.py @@ -0,0 +1,26 @@ +from django.db import models + +class OpenIdNonce(models.Model): + server_url = models.URLField() + timestamp = models.IntegerField() + salt = models.CharField( max_length=50 ) + + def __unicode__(self): + return "Nonce: %s" % self.nonce + + class Meta: + app_label = 'forum' + +class OpenIdAssociation(models.Model): + server_url = models.TextField(max_length=2047) + handle = models.CharField(max_length=255) + secret = models.TextField(max_length=255) # Stored base64 encoded + issued = models.IntegerField() + lifetime = models.IntegerField() + assoc_type = models.TextField(max_length=64) + + def __unicode__(self): + return "Association: %s, %s" % (self.server_url, self.handle) + + class Meta: + app_label = 'forum' diff --git a/forum_modules/openidauth/settings.py b/forum_modules/openidauth/settings.py new file mode 100755 index 00000000..f7c641a1 --- /dev/null +++ b/forum_modules/openidauth/settings.py @@ -0,0 +1,9 @@ +OPENID_SREG = { + "required": "nickname, email", + "optional": "postcode, country", + "policy_url": "" +} +OPENID_AX = [ + {"type_uri": "http://axschema.org/contact/email", "count": 1, "required": True, "alias": "email"}, + {"type_uri": "fullname", "count":1 , "required": False, "alias": "fullname"} + ]
\ No newline at end of file diff --git a/forum_modules/openidauth/store.py b/forum_modules/openidauth/store.py new file mode 100755 index 00000000..fa61ea6d --- /dev/null +++ b/forum_modules/openidauth/store.py @@ -0,0 +1,79 @@ +import time, base64, md5 + +from openid.store import nonce as oid_nonce +from openid.store.interface import OpenIDStore +from openid.association import Association as OIDAssociation +from django.conf import settings + +from models import OpenIdNonce as Nonce, OpenIdAssociation as Association + +class OsqaOpenIDStore(OpenIDStore): + def __init__(self): + self.max_nonce_age = 6 * 60 * 60 # Six hours + + def storeAssociation(self, server_url, association): + assoc = Association( + server_url = server_url, + handle = association.handle, + secret = base64.encodestring(association.secret), + issued = association.issued, + lifetime = association.issued, + assoc_type = association.assoc_type + ) + assoc.save() + + def getAssociation(self, server_url, handle=None): + assocs = [] + if handle is not None: + assocs = Association.objects.filter( + server_url = server_url, handle = handle + ) + else: + assocs = Association.objects.filter( + server_url = server_url + ) + if not assocs: + return None + associations = [] + for assoc in assocs: + association = OIDAssociation( + assoc.handle, base64.decodestring(assoc.secret), assoc.issued, + assoc.lifetime, assoc.assoc_type + ) + if association.getExpiresIn() == 0: + self.removeAssociation(server_url, assoc.handle) + else: + associations.append((association.issued, association)) + if not associations: + return None + return associations[-1][1] + + def removeAssociation(self, server_url, handle): + assocs = list(Association.objects.filter( + server_url = server_url, handle = handle + )) + assocs_exist = len(assocs) > 0 + for assoc in assocs: + assoc.delete() + return assocs_exist + + def storeNonce(self, nonce): + nonce, created = Nonce.objects.get_or_create( + nonce = nonce, defaults={'expires': int(time.time())} + ) + + def useNonce(self, server_url, timestamp, salt): + if abs(timestamp - time.time()) > oid_nonce.SKEW: + return False + + try: + nonce = Nonce( server_url=server_url, timestamp=timestamp, salt=salt) + nonce.save() + except: + raise + else: + return 1 + + def getAuthKey(self): + # Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY + return md5.new(settings.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN] diff --git a/forum_modules/openidauth/templates/openidurl.html b/forum_modules/openidauth/templates/openidurl.html new file mode 100755 index 00000000..cd4e77dc --- /dev/null +++ b/forum_modules/openidauth/templates/openidurl.html @@ -0,0 +1,20 @@ +{% load i18n %}
+{% load extra_tags %}
+
+<fieldset>
+ <table>
+ <tr>
+ <td><p id="provider_name_slot">{% trans 'Enter your OpenId Url' %}</p></td>
+ </tr>
+ <tr>
+ <td>
+ <input id="openid_identifier" class="icon_input" name="openid_identifier" type="text"
+ style="width: 500px; background: url('{% media provider.icon %}') no-repeat left center" />
+ </td>
+ <td>
+ <input type="submit" name="ssignin" value="Login" />
+ </td>
+ </tr>
+ </table>
+</fieldset>
+
diff --git a/forum_modules/pgfulltext/DISABLED b/forum_modules/pgfulltext/DISABLED new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/forum_modules/pgfulltext/DISABLED diff --git a/forum_modules/robotstxt/DISABLED b/forum_modules/robotstxt/DISABLED new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/forum_modules/robotstxt/DISABLED diff --git a/forum_modules/robotstxt/__init__.py b/forum_modules/robotstxt/__init__.py new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/forum_modules/robotstxt/__init__.py diff --git a/forum_modules/robotstxt/templates/robots.txt b/forum_modules/robotstxt/templates/robots.txt new file mode 100755 index 00000000..574fc315 --- /dev/null +++ b/forum_modules/robotstxt/templates/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /
\ No newline at end of file diff --git a/forum_modules/robotstxt/urls.py b/forum_modules/robotstxt/urls.py new file mode 100755 index 00000000..79a6d84c --- /dev/null +++ b/forum_modules/robotstxt/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import * +from django.views.generic.simple import direct_to_template + +urlpatterns = patterns('', + (r'^robots.txt$', direct_to_template, {'template': 'modules/robotsdennyall/robots.txt'}), +) diff --git a/forum_modules/sphinxfulltext/models.py b/forum_modules/sphinxfulltext/models.py index 9db4aa86..a188728d 100755 --- a/forum_modules/sphinxfulltext/models.py +++ b/forum_modules/sphinxfulltext/models.py @@ -2,6 +2,7 @@ from forum.models import Question from django.conf import settings from djangosphinx.manager import SphinxSearch +from djangosphinx.models import SphinxSearch Question.add_to_class('search', SphinxSearch( index=' '.join(settings.SPHINX_SEARCH_INDICES), diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo Binary files differindex 3107733e..cc6d50e7 100644 --- a/locale/en/LC_MESSAGES/django.mo +++ b/locale/en/LC_MESSAGES/django.mo diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 0ad97472..3f1c187f 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-03-24 14:27-0400\n" +"POT-Creation-Date: 2010-03-27 11:18-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -16,158 +16,6 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: deps/recaptcha_django/__init__.py:18 deps/recaptcha_django/__init__.py:75 -msgid "Unknown error." -msgstr "" - -#: deps/recaptcha_django/__init__.py:19 deps/recaptcha_django/__init__.py:20 -msgid "ReCAPTCHA is wrongly configured." -msgstr "" - -#: deps/recaptcha_django/__init__.py:21 -msgid "Bad reCAPTCHA challenge parameter." -msgstr "" - -#: deps/recaptcha_django/__init__.py:22 -msgid "The CAPTCHA solution was incorrect." -msgstr "" - -#: deps/recaptcha_django/__init__.py:23 -msgid "Bad reCAPTCHA verification parameters." -msgstr "" - -#: deps/recaptcha_django/__init__.py:24 -msgid "Provided reCAPTCHA API keys are not valid for this domain." -msgstr "" - -#: deps/recaptcha_django/__init__.py:25 -msgid "ReCAPTCHA could not be reached." -msgstr "" - -#: deps/recaptcha_django/__init__.py:68 -msgid "Invalid request" -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/admin.py:9 -#: django-multilingual-read-only/multilingual/flatpages/models.py:9 -msgid "URL" -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/admin.py:10 -msgid "" -"Example: '/about/contact/'. Make sure to have leading and trailing slashes." -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/admin.py:12 -msgid "" -"This value must contain only letters, numbers, underscores, dashes or " -"slashes." -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/admin.py:23 -msgid "Advanced options" -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/models.py:10 -msgid "enable comments" -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/models.py:11 -msgid "template name" -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/models.py:12 -msgid "" -"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " -"will use 'flatpages/default.html'." -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/models.py:13 -msgid "registration required" -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/models.py:13 -msgid "If this is checked, only logged-in users will be able to view the page." -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/models.py:30 -#: forum/forms.py:20 forum/skins/default/templates/answer_edit_tips.html:35 -#: forum/skins/default/templates/answer_edit_tips.html:39 -#: forum/skins/default/templates/question_edit_tips.html:32 -#: forum/skins/default/templates/question_edit_tips.html:37 -msgid "title" -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/models.py:31 -#: forum/forms.py:35 -msgid "content" -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/models.py:35 -msgid "multilingual flat page" -msgstr "" - -#: django-multilingual-read-only/multilingual/flatpages/models.py:36 -msgid "multilingual flat pages" -msgstr "" - -#: django-multilingual-read-only/testproject/articles/models.py:267 -#: django-multilingual-read-only/testproject/articles/models.py:325 -msgid "Created by" -msgstr "" - -#: django-multilingual-read-only/testproject/articles/models.py:269 -#: django-multilingual-read-only/testproject/articles/models.py:327 -msgid "Created at" -msgstr "" - -#: django-multilingual-read-only/testproject/articles/models.py:271 -#: django-multilingual-read-only/testproject/articles/models.py:329 -#: django-multilingual-read-only/testproject/issue_16/models/category.py:11 -#: django-multilingual-read-only/testproject/issue_16/models/page.py:12 -msgid "Parent category" -msgstr "" - -#: django-multilingual-read-only/testproject/articles/models.py:289 -#: django-multilingual-read-only/testproject/issue_16/models/category.py:15 -msgid "The name" -msgstr "" - -#: django-multilingual-read-only/testproject/articles/models.py:291 -msgid "The description" -msgstr "" - -#: django-multilingual-read-only/testproject/articles/models.py:336 -#: django-multilingual-read-only/testproject/issue_16/models/page.py:16 -msgid "The title" -msgstr "" - -#: django-multilingual-read-only/testproject/articles/models.py:338 -#: django-multilingual-read-only/testproject/issue_16/models/page.py:18 -msgid "The contents" -msgstr "" - -#: django-multilingual-read-only/testproject/issue_15/models.py:36 -#: django-multilingual-read-only/testproject/issue_29/models.py:36 -msgid "Parent gallery" -msgstr "" - -#: django-multilingual-read-only/testproject/issue_15/models.py:37 -#: django-multilingual-read-only/testproject/issue_29/models.py:38 -msgid "Modified" -msgstr "" - -#: django-multilingual-read-only/testproject/issue_15/models.py:40 -#: django-multilingual-read-only/testproject/issue_29/models.py:41 -#: django-multilingual-read-only/testproject/issue_37/models.py:34 -msgid "Title" -msgstr "" - -#: django-multilingual-read-only/testproject/issue_15/models.py:41 -#: django-multilingual-read-only/testproject/issue_29/models.py:42 -msgid "Description" -msgstr "" - #: django_authopenid/forms.py:71 django_authopenid/views.py:118 msgid "i-names are not supported" msgstr "" @@ -180,27 +28,28 @@ msgstr "" msgid "can't have two logins to the same account yet, sorry." msgstr "" -#: django_authopenid/forms.py:157 +#: django_authopenid/forms.py:157 forum_modules/localauth/forms.py:59 msgid "Please enter valid username and password (both are case-sensitive)." msgstr "" #: django_authopenid/forms.py:160 django_authopenid/forms.py:210 +#: forum_modules/localauth/forms.py:62 msgid "This account is inactive." msgstr "" -#: django_authopenid/forms.py:162 +#: django_authopenid/forms.py:162 forum_modules/localauth/forms.py:64 msgid "Login failed." msgstr "" -#: django_authopenid/forms.py:164 +#: django_authopenid/forms.py:164 forum_modules/localauth/forms.py:66 msgid "Please enter username and password" msgstr "" -#: django_authopenid/forms.py:166 +#: django_authopenid/forms.py:166 forum_modules/localauth/forms.py:68 msgid "Please enter your password" msgstr "" -#: django_authopenid/forms.py:168 +#: django_authopenid/forms.py:168 forum_modules/localauth/forms.py:70 msgid "Please enter user name" msgstr "" @@ -210,11 +59,11 @@ msgid "" "both fields are case-sensitive." msgstr "" -#: django_authopenid/forms.py:229 +#: django_authopenid/forms.py:229 forum/authentication/forms.py:60 msgid "Current password" msgstr "" -#: django_authopenid/forms.py:240 +#: django_authopenid/forms.py:240 forum/authentication/forms.py:71 msgid "" "Old password is incorrect. Please enter the correct " "password." @@ -231,7 +80,7 @@ msgstr "sorry, there is no such user name" #: django_authopenid/urls.py:23 django_authopenid/urls.py:24 #: django_authopenid/urls.py:25 django_authopenid/urls.py:27 #: fbconnect/urls.py:12 fbconnect/urls.py:13 fbconnect/urls.py:14 -#: forum/urls.py:32 +#: forum/urls.py:93 forum/urls.py:95 forum/urls.py:96 msgid "signin/" msgstr "" @@ -243,7 +92,7 @@ msgstr "" msgid "newanswer/" msgstr "" -#: django_authopenid/urls.py:26 +#: django_authopenid/urls.py:26 forum/urls.py:94 msgid "signout/" msgstr "" @@ -252,7 +101,7 @@ msgid "complete/" msgstr "" #: django_authopenid/urls.py:29 fbconnect/urls.py:16 fbconnect/urls.py:17 -#: fbconnect/urls.py:18 +#: fbconnect/urls.py:18 forum/urls.py:98 forum_modules/localauth/urls.py:7 msgid "register/" msgstr "" @@ -281,7 +130,7 @@ msgstr "" msgid "email/" msgstr "" -#: django_authopenid/urls.py:38 +#: django_authopenid/urls.py:38 forum/urls.py:99 msgid "validate/" msgstr "" @@ -394,19 +243,19 @@ msgstr "" msgid "Password changed for %s. You may now sign in." msgstr "" -#: forum/auth.py:484 +#: forum/auth.py:505 msgid "Your question and all of it's answers have been deleted" msgstr "" -#: forum/auth.py:486 +#: forum/auth.py:507 msgid "Your question has been deleted" msgstr "" -#: forum/auth.py:489 +#: forum/auth.py:510 msgid "The question and all of it's answers have been deleted" msgstr "" -#: forum/auth.py:491 +#: forum/auth.py:512 msgid "The question has been deleted" msgstr "" @@ -423,7 +272,7 @@ msgid "too subjective and argumentative" msgstr "" #: forum/const.py:11 -msgid "is not an answer to the question" +msgid "not a real question" msgstr "" #: forum/const.py:12 @@ -431,15 +280,19 @@ msgid "the question is answered, right answer was accepted" msgstr "" #: forum/const.py:13 -msgid "problem is not reproducible or outdated" +msgid "question is not relevant or outdated" +msgstr "" + +#: forum/const.py:14 +msgid "question contains offensive or malicious remarks" msgstr "" #: forum/const.py:15 -msgid "question contains offensive inappropriate, or malicious remarks" +msgid "spam or advertising" msgstr "" #: forum/const.py:16 -msgid "spam or advertising" +msgid "too localized" msgstr "" #: forum/const.py:57 @@ -514,6 +367,22 @@ msgstr "" msgid "email update sent to user" msgstr "" +#: forum/const.py:78 +msgid "question_answered" +msgstr "answered question" + +#: forum/const.py:79 +msgid "question_commented" +msgstr "commented question" + +#: forum/const.py:80 +msgid "answer_commented" +msgstr "" + +#: forum/const.py:81 +msgid "answer_accepted" +msgstr "" + #: forum/const.py:85 msgid "[closed]" msgstr "" @@ -522,7 +391,7 @@ msgstr "" msgid "[deleted]" msgstr "" -#: forum/const.py:87 forum/views/readers.py:564 forum/views/readers.py:583 +#: forum/const.py:87 forum/views/readers.py:565 forum/views/readers.py:584 msgid "initial version" msgstr "" @@ -546,47 +415,58 @@ msgstr "" msgid "latest questions" msgstr "" -#: forum/forms.py:21 +#: forum/forms.py:22 forum/skins/default/templates/answer_edit_tips.html:35 +#: forum/skins/default/templates/answer_edit_tips.html:39 +#: forum/skins/default/templates/question_edit_tips.html:32 +#: forum/skins/default/templates/question_edit_tips.html:37 +msgid "title" +msgstr "" + +#: forum/forms.py:23 msgid "please enter a descriptive title for your question" msgstr "" -#: forum/forms.py:26 +#: forum/forms.py:28 msgid "title must be > 10 characters" msgstr "" -#: forum/forms.py:41 +#: forum/forms.py:37 +msgid "content" +msgstr "" + +#: forum/forms.py:43 msgid "question content must be > 10 characters" msgstr "" -#: forum/forms.py:51 forum/skins/default/templates/header.html:32 -#: forum/skins/default/templates/header.html:60 +#: forum/forms.py:52 forum/skins/default/templates/header.html:28 +#: forum/skins/default/templates/header.html:56 msgid "tags" msgstr "" -#: forum/forms.py:53 +#: forum/forms.py:54 msgid "" "Tags are short keywords, with no spaces within. Up to five tags can be used." msgstr "" -#: forum/forms.py:60 forum/skins/default/templates/question_retag.html:39 +#: forum/forms.py:61 forum/skins/default/templates/question_retag.html:39 msgid "tags are required" msgstr "" -#: forum/forms.py:66 +#: forum/forms.py:67 msgid "please use 5 tags or less" msgstr "" -#: forum/forms.py:69 +#: forum/forms.py:70 msgid "tags must be shorter than 20 characters" msgstr "" -#: forum/forms.py:73 +#: forum/forms.py:74 msgid "" "please use following characters in tags: letters 'a-z', numbers, and " "characters '.-_#'" msgstr "" -#: forum/forms.py:83 +#: forum/forms.py:84 #: forum/skins/default/templates/post_contributor_info.html:7 #: forum/skins/default/templates/question_summary_list_roll.html:26 #: forum/skins/default/templates/question_summary_list_roll.html:38 @@ -595,116 +475,116 @@ msgstr "" msgid "community wiki" msgstr "" -#: forum/forms.py:84 +#: forum/forms.py:85 msgid "" "if you choose community wiki option, the question and answer do not generate " "points and name of author will not be shown" msgstr "" -#: forum/forms.py:100 +#: forum/forms.py:101 msgid "update summary:" msgstr "" -#: forum/forms.py:101 +#: forum/forms.py:102 msgid "" "enter a brief summary of your revision (e.g. fixed spelling, grammar, " "improved style, this field is optional)" msgstr "" -#: forum/forms.py:104 +#: forum/forms.py:105 msgid "Automatically accept user's contributions for the email updates" msgstr "" -#: forum/forms.py:120 +#: forum/forms.py:121 msgid "Your name:" msgstr "" -#: forum/forms.py:121 +#: forum/forms.py:122 msgid "Email (not shared with anyone):" msgstr "" -#: forum/forms.py:122 +#: forum/forms.py:123 msgid "Your message:" msgstr "" -#: forum/forms.py:204 +#: forum/forms.py:206 msgid "this email does not have to be linked to gravatar" msgstr "" -#: forum/forms.py:206 +#: forum/forms.py:208 msgid "Screen name" msgstr "" -#: forum/forms.py:207 +#: forum/forms.py:209 msgid "Real name" msgstr "" -#: forum/forms.py:208 +#: forum/forms.py:210 msgid "Website" msgstr "" -#: forum/forms.py:209 +#: forum/forms.py:211 msgid "Location" msgstr "" -#: forum/forms.py:210 +#: forum/forms.py:212 msgid "Date of birth" msgstr "" -#: forum/forms.py:210 +#: forum/forms.py:212 msgid "will not be shown, used to calculate age, format: YYYY-MM-DD" msgstr "" -#: forum/forms.py:211 +#: forum/forms.py:213 forum/skins/default/templates/account_settings.html:21 #: forum/skins/default/templates/authopenid/settings.html:21 msgid "Profile" msgstr "" -#: forum/forms.py:242 forum/forms.py:243 +#: forum/forms.py:244 forum/forms.py:245 msgid "this email has already been registered, please use another one" msgstr "" -#: forum/forms.py:249 +#: forum/forms.py:251 msgid "Choose email tag filter" msgstr "" -#: forum/forms.py:264 forum/forms.py:265 +#: forum/forms.py:267 forum/forms.py:268 msgid "weekly" msgstr "" -#: forum/forms.py:264 forum/forms.py:265 +#: forum/forms.py:267 forum/forms.py:268 msgid "no email" msgstr "" -#: forum/forms.py:265 +#: forum/forms.py:268 msgid "daily" msgstr "" -#: forum/forms.py:280 +#: forum/forms.py:283 msgid "Asked by me" msgstr "" -#: forum/forms.py:283 +#: forum/forms.py:286 msgid "Answered by me" msgstr "" -#: forum/forms.py:286 +#: forum/forms.py:289 msgid "Individually selected" msgstr "" -#: forum/forms.py:289 +#: forum/forms.py:292 msgid "Entire forum (tag filtered)" msgstr "" -#: forum/forms.py:343 +#: forum/forms.py:346 forum/authentication/forms.py:41 msgid "okay, let's try!" msgstr "" -#: forum/forms.py:344 +#: forum/forms.py:347 msgid "no community email please, thanks" msgstr "no askbot email please, thanks" -#: forum/forms.py:347 +#: forum/forms.py:350 forum/authentication/forms.py:45 msgid "please choose one of the options above" msgstr "" @@ -779,7 +659,7 @@ msgstr "" msgid "command/" msgstr "" -#: forum/urls.py:60 forum/views/readers.py:435 +#: forum/urls.py:60 forum/views/readers.py:436 msgid "question/" msgstr "" @@ -839,15 +719,62 @@ msgstr "" msgid "feedback/" msgstr "" -#: forum/urls.py:89 forum/urls.py:90 +#: forum/urls.py:93 forum/urls.py:94 forum/urls.py:95 forum/urls.py:96 +#: forum/urls.py:97 forum/urls.py:98 forum/urls.py:99 forum/urls.py:100 +#: forum/urls.py:101 forum/urls.py:102 forum/urls.py:103 forum/urls.py:104 +#: forum_modules/localauth/urls.py:7 msgid "account/" msgstr "" -#: forum/management/commands/send_email_alerts.py:156 +#: forum/urls.py:97 +msgid "done/" +msgstr "" + +#: forum/urls.py:100 forum/urls.py:101 +msgid "tempsignin/" +msgstr "" + +#: forum/urls.py:102 +msgid "authsettings/" +msgstr "" + +#: forum/urls.py:103 forum/urls.py:104 +msgid "providers/" +msgstr "" + +#: forum/urls.py:103 +msgid "remove/" +msgstr "" + +#: forum/urls.py:104 +msgid "add/" +msgstr "" + +#: forum/authentication/forms.py:22 +msgid "Your account email" +msgstr "" + +#: forum/authentication/forms.py:24 +msgid "You cannot leave this field blank" +msgstr "" + +#: forum/authentication/forms.py:25 forum/utils/forms.py:111 +msgid "please enter a valid email address" +msgstr "" + +#: forum/authentication/forms.py:33 +msgid "Sorry, but this email is not on our database." +msgstr "" + +#: forum/authentication/forms.py:42 +msgid "no OSQA community email please, thanks" +msgstr "" + +#: forum/management/commands/send_email_alerts.py:236 msgid "email update message subject" msgstr "news from Q&A forum" -#: forum/management/commands/send_email_alerts.py:158 +#: forum/management/commands/send_email_alerts.py:238 #, python-format msgid "%(name)s, this is an update message header for a question" msgid_plural "%(name)s, this is an update message header for %(num)d questions" @@ -858,24 +785,38 @@ msgstr[1] "" "<p>Dear %(name)s,</p><p>The following %(num)d questions have been updated on " "the Q&A forum:</p>" -#: forum/management/commands/send_email_alerts.py:169 +#: forum/management/commands/send_email_alerts.py:255 msgid "new question" msgstr "" -#: forum/management/commands/send_email_alerts.py:179 -#, python-format -msgid "There is also one question which was recently " -msgid_plural "" -"There are also %(num)d more questions which were recently updated " -msgstr[0] "" -msgstr[1] "" +#: forum/management/commands/send_email_alerts.py:272 +msgid "" +"Please visit the forum and see what's new! Could you spread the word about " +"it - can somebody you know help answering those questions or benefit from " +"posting one?" +msgstr "" + +#: forum/management/commands/send_email_alerts.py:284 +msgid "" +"Your most frequent subscription setting is 'daily' on selected questions. If " +"you are receiving more than one email per dayplease tell about this issue to " +"the forum administrator." +msgstr "" -#: forum/management/commands/send_email_alerts.py:184 +#: forum/management/commands/send_email_alerts.py:290 msgid "" -"Perhaps you could look up previously sent forum reminders in your mailbox." +"Your most frequent subscription setting is 'weekly' if you are receiving " +"this email more than once a week please report this issue to the forum " +"administrator." msgstr "" -#: forum/management/commands/send_email_alerts.py:188 +#: forum/management/commands/send_email_alerts.py:296 +msgid "" +"There is a chance that you may be receiving links seen before - due to a " +"technicality that will eventually go away. " +msgstr "" + +#: forum/management/commands/send_email_alerts.py:311 #, python-format msgid "" "go to %(link)s to change frequency of email updates or %(email)s " @@ -887,45 +828,45 @@ msgstr "" "administrator at %(email)s.</p><p>Sincerely,</p><p>Your friendly Q&A forum " "server.</p>" -#: forum/middleware/anon_user.py:33 +#: forum/middleware/anon_user.py:34 #, python-format -msgid "first time greeting with %(url)s" +msgid "First time here? Check out the <a href=\"%s\">FAQ</a>!" msgstr "" -#: forum/models/question.py:247 +#: forum/models/question.py:362 #, python-format msgid "%(author)s modified the question" msgstr "" -#: forum/models/question.py:251 +#: forum/models/question.py:366 #, python-format msgid "%(people)s posted %(new_answer_count)s new answers" msgstr "" -#: forum/models/question.py:256 +#: forum/models/question.py:371 #, python-format msgid "%(people)s commented the question" msgstr "" -#: forum/models/question.py:261 +#: forum/models/question.py:376 #, python-format msgid "%(people)s commented answers" msgstr "" -#: forum/models/question.py:263 +#: forum/models/question.py:378 #, python-format msgid "%(people)s commented an answer" msgstr "" -#: forum/models/repute.py:11 forum/skins/default/templates/badges.html:53 +#: forum/models/repute.py:13 forum/skins/default/templates/badges.html:53 msgid "gold" msgstr "" -#: forum/models/repute.py:12 forum/skins/default/templates/badges.html:61 +#: forum/models/repute.py:14 forum/skins/default/templates/badges.html:61 msgid "silver" msgstr "" -#: forum/models/repute.py:13 forum/skins/default/templates/badges.html:68 +#: forum/models/repute.py:15 forum/skins/default/templates/badges.html:68 msgid "bronze" msgstr "" @@ -937,31 +878,31 @@ msgstr "" msgid "ignored" msgstr "" -#: forum/models/user.py:31 +#: forum/models/user.py:36 msgid "Entire forum" msgstr "" -#: forum/models/user.py:32 +#: forum/models/user.py:37 msgid "Questions that I asked" msgstr "" -#: forum/models/user.py:33 +#: forum/models/user.py:38 msgid "Questions that I answered" msgstr "" -#: forum/models/user.py:34 +#: forum/models/user.py:39 msgid "Individually selected questions" msgstr "" -#: forum/models/user.py:37 +#: forum/models/user.py:42 msgid "Weekly" msgstr "" -#: forum/models/user.py:38 +#: forum/models/user.py:43 msgid "Daily" msgstr "" -#: forum/models/user.py:39 +#: forum/models/user.py:44 msgid "No email" msgstr "" @@ -1033,6 +974,57 @@ msgstr "" msgid "About" msgstr "" +#: forum/skins/default/templates/account_settings.html:4 +#: forum/skins/default/templates/authopenid/settings.html:4 +msgid "Account functions" +msgstr "" + +#: forum/skins/default/templates/account_settings.html:29 +#: forum/skins/default/templates/auth/auth_settings.html:31 +#: forum/skins/default/templates/authopenid/changepw.html:5 +#: forum/skins/default/templates/authopenid/changepw.html:14 +#: forum/skins/default/templates/authopenid/settings.html:29 +msgid "Change password" +msgstr "" + +#: forum/skins/default/templates/account_settings.html:30 +#: forum/skins/default/templates/authopenid/settings.html:30 +msgid "Give your account a new password." +msgstr "" + +#: forum/skins/default/templates/account_settings.html:32 +#: forum/skins/default/templates/authopenid/settings.html:31 +msgid "Change email " +msgstr "" + +#: forum/skins/default/templates/account_settings.html:33 +#: forum/skins/default/templates/authopenid/settings.html:32 +msgid "Add or update the email address associated with your account." +msgstr "" + +#: forum/skins/default/templates/account_settings.html:35 +#: forum/skins/default/templates/authopenid/changeopenid.html:4 +#: forum/skins/default/templates/authopenid/changeopenid.html:30 +#: forum/skins/default/templates/authopenid/settings.html:34 +msgid "Change OpenID" +msgstr "" + +#: forum/skins/default/templates/account_settings.html:36 +#: forum/skins/default/templates/authopenid/settings.html:35 +msgid "Change openid associated to your account" +msgstr "" + +#: forum/skins/default/templates/account_settings.html:39 +#: forum/skins/default/templates/authopenid/delete.html:4 +#: forum/skins/default/templates/authopenid/settings.html:38 +msgid "Delete account" +msgstr "" + +#: forum/skins/default/templates/account_settings.html:40 +#: forum/skins/default/templates/authopenid/settings.html:39 +msgid "Erase your username and all your data from website" +msgstr "" + #: forum/skins/default/templates/answer_edit.html:5 #: forum/skins/default/templates/answer_edit.html:48 msgid "Edit answer" @@ -1042,8 +1034,8 @@ msgstr "" #: forum/skins/default/templates/answer_edit.html:28 #: forum/skins/default/templates/ask.html:26 #: forum/skins/default/templates/ask.html:29 -#: forum/skins/default/templates/question.html:45 -#: forum/skins/default/templates/question.html:48 +#: forum/skins/default/templates/question.html:46 +#: forum/skins/default/templates/question.html:49 #: forum/skins/default/templates/question_edit.html:25 #: forum/skins/default/templates/question_edit.html:28 msgid "hide preview" @@ -1051,7 +1043,7 @@ msgstr "" #: forum/skins/default/templates/answer_edit.html:28 #: forum/skins/default/templates/ask.html:29 -#: forum/skins/default/templates/question.html:48 +#: forum/skins/default/templates/question.html:49 #: forum/skins/default/templates/question_edit.html:28 msgid "show preview" msgstr "" @@ -1078,14 +1070,14 @@ msgstr "" #: forum/skins/default/templates/answer_edit.html:63 #: forum/skins/default/templates/ask.html:97 -#: forum/skins/default/templates/question.html:430 +#: forum/skins/default/templates/question.html:431 #: forum/skins/default/templates/question_edit.html:92 msgid "Toggle the real time Markdown editor preview" msgstr "" #: forum/skins/default/templates/answer_edit.html:63 #: forum/skins/default/templates/ask.html:97 -#: forum/skins/default/templates/question.html:431 +#: forum/skins/default/templates/question.html:432 #: forum/skins/default/templates/question_edit.html:92 msgid "toggle preview" msgstr "" @@ -1353,8 +1345,8 @@ msgid "number of times" msgstr "" #: forum/skins/default/templates/book.html:105 -#: forum/skins/default/templates/index.html:40 #: forum/skins/default/templates/index_.html:40 +#: forum/skins/default/templates/question_list.html:14 #: forum/skins/default/templates/question_summary_list_roll.html:14 #: forum/skins/default/templates/questions.html:80 #: forum/skins/default/templates/users_questions.html:32 @@ -1366,8 +1358,8 @@ msgid "the answer has been accepted to be correct" msgstr "" #: forum/skins/default/templates/book.html:115 -#: forum/skins/default/templates/index.html:48 #: forum/skins/default/templates/index_.html:48 +#: forum/skins/default/templates/question_list.html:22 #: forum/skins/default/templates/question_summary_list_roll.html:15 #: forum/skins/default/templates/questions.html:81 #: forum/skins/default/templates/users_questions.html:40 @@ -1375,9 +1367,9 @@ msgid "views" msgstr "" #: forum/skins/default/templates/book.html:125 -#: forum/skins/default/templates/index.html:63 #: forum/skins/default/templates/index_.html:63 -#: forum/skins/default/templates/question.html:476 +#: forum/skins/default/templates/question.html:478 +#: forum/skins/default/templates/question_list.html:36 #: forum/skins/default/templates/question_summary_list_roll.html:52 #: forum/skins/default/templates/questions.html:136 #: forum/skins/default/templates/tags.html:49 @@ -1390,7 +1382,7 @@ msgid "subscribe to book RSS feed" msgstr "" #: forum/skins/default/templates/book.html:147 -#: forum/skins/default/templates/index.html:114 +#: forum/skins/default/templates/index.html:85 #: forum/skins/default/templates/index_.html:114 msgid "subscribe to the questions feed" msgstr "" @@ -1412,6 +1404,10 @@ msgstr "" msgid "OK to close" msgstr "" +#: forum/skins/default/templates/email_base.html:11 +msgid "home" +msgstr "" + #: forum/skins/default/templates/faq.html:11 msgid "Frequently Asked Questions " msgstr "" @@ -1548,11 +1544,11 @@ msgstr "" msgid "delete any questions and answers and perform other moderation tasks" msgstr "" -#: forum/skins/default/templates/faq.html:102 +#: forum/skins/default/templates/faq.html:103 msgid "how to validate email title" msgstr "How to validate email and why?" -#: forum/skins/default/templates/faq.html:104 +#: forum/skins/default/templates/faq.html:105 #, python-format msgid "" "how to validate email info with %(send_email_key_url)s %(gravatar_faq_url)s" @@ -1570,11 +1566,11 @@ msgstr "" "Also, when you sign up for the first time - create a unique <a href='%" "(gravatar_faq_url)s'><strong>gravatar</strong></a> personal image.</p>" -#: forum/skins/default/templates/faq.html:108 +#: forum/skins/default/templates/faq.html:110 msgid "what is gravatar" msgstr "What is gravatar?" -#: forum/skins/default/templates/faq.html:109 +#: forum/skins/default/templates/faq.html:111 msgid "gravatar faq info" msgstr "" "<strong>Gravatar</strong> means <strong>g</strong>lobally <strong>r</" @@ -1585,44 +1581,44 @@ msgstr "" "your image</strong> at <a href='http://gravatar.com'><strong>gravatar.com</" "strong></a>" -#: forum/skins/default/templates/faq.html:112 +#: forum/skins/default/templates/faq.html:114 msgid "To register, do I need to create new password?" msgstr "" -#: forum/skins/default/templates/faq.html:113 +#: forum/skins/default/templates/faq.html:115 msgid "" "No, you don't have to. You can login through any service that supports " "OpenID, e.g. Google, Yahoo, AOL, etc." msgstr "" -#: forum/skins/default/templates/faq.html:114 +#: forum/skins/default/templates/faq.html:116 msgid "Login now!" msgstr "" -#: forum/skins/default/templates/faq.html:119 +#: forum/skins/default/templates/faq.html:121 msgid "Why other people can edit my questions/answers?" msgstr "" -#: forum/skins/default/templates/faq.html:120 +#: forum/skins/default/templates/faq.html:122 msgid "Goal of this site is..." msgstr "" -#: forum/skins/default/templates/faq.html:120 +#: forum/skins/default/templates/faq.html:122 msgid "" "So questions and answers can be edited like wiki pages by experienced users " "of this site and this improves the overall quality of the knowledge base " "content." msgstr "" -#: forum/skins/default/templates/faq.html:121 +#: forum/skins/default/templates/faq.html:123 msgid "If this approach is not for you, we respect your choice." msgstr "" -#: forum/skins/default/templates/faq.html:125 +#: forum/skins/default/templates/faq.html:127 msgid "Still have questions?" msgstr "" -#: forum/skins/default/templates/faq.html:126 +#: forum/skins/default/templates/faq.html:128 #, python-format msgid "" "Please ask your question at %(ask_question_url)s, help make our community " @@ -1631,14 +1627,14 @@ msgstr "" "Please <a href='%(ask_question_url)s'>ask</a> your question, help make our " "community better!" -#: forum/skins/default/templates/faq.html:128 -#: forum/skins/default/templates/header.html:31 -#: forum/skins/default/templates/header.html:59 +#: forum/skins/default/templates/faq.html:130 +#: forum/skins/default/templates/header.html:27 +#: forum/skins/default/templates/header.html:55 msgid "questions" msgstr "" -#: forum/skins/default/templates/faq.html:128 -#: forum/skins/default/templates/index.html:121 +#: forum/skins/default/templates/faq.html:130 +#: forum/skins/default/templates/index.html:92 #: forum/skins/default/templates/index_.html:121 msgid "." msgstr "" @@ -1703,15 +1699,15 @@ msgid "Message body:" msgstr "" #: forum/skins/default/templates/footer.html:8 -#: forum/skins/default/templates/header.html:14 -#: forum/skins/default/templates/index.html:77 +#: forum/skins/default/templates/header.html:13 +#: forum/skins/default/templates/index.html:44 #: forum/skins/default/templates/index_.html:77 msgid "about" msgstr "" #: forum/skins/default/templates/footer.html:9 -#: forum/skins/default/templates/header.html:15 -#: forum/skins/default/templates/index.html:78 +#: forum/skins/default/templates/header.html:14 +#: forum/skins/default/templates/index.html:45 #: forum/skins/default/templates/index_.html:78 #: forum/skins/default/templates/question_edit_tips.html:17 msgid "faq" @@ -1733,180 +1729,177 @@ msgstr "" msgid "login" msgstr "" -#: forum/skins/default/templates/header.html:24 +#: forum/skins/default/templates/header.html:21 msgid "back to home page" msgstr "" -#: forum/skins/default/templates/header.html:33 -#: forum/skins/default/templates/header.html:61 +#: forum/skins/default/templates/header.html:29 +#: forum/skins/default/templates/header.html:57 msgid "users" msgstr "" -#: forum/skins/default/templates/header.html:35 +#: forum/skins/default/templates/header.html:31 msgid "books" msgstr "" -#: forum/skins/default/templates/header.html:37 -#: forum/templatetags/extra_tags.py:165 forum/templatetags/extra_tags.py:194 +#: forum/skins/default/templates/header.html:33 +#: forum/templatetags/extra_tags.py:166 forum/templatetags/extra_tags.py:195 msgid "badges" msgstr "" -#: forum/skins/default/templates/header.html:38 +#: forum/skins/default/templates/header.html:34 msgid "unanswered questions" msgstr "unanswered" -#: forum/skins/default/templates/header.html:40 +#: forum/skins/default/templates/header.html:36 msgid "ask a question" msgstr "" -#: forum/skins/default/templates/header.html:55 +#: forum/skins/default/templates/header.html:51 msgid "search" msgstr "" -#: forum/skins/default/templates/index.html:8 +#: forum/skins/default/templates/index.html:9 #: forum/skins/default/templates/index_.html:8 msgid "Home" msgstr "" -#: forum/skins/default/templates/index.html:25 +#: forum/skins/default/templates/index.html:26 #: forum/skins/default/templates/index_.html:25 #: forum/skins/default/templates/questions.html:8 msgid "Questions" msgstr "" -#: forum/skins/default/templates/index.html:27 +#: forum/skins/default/templates/index.html:28 #: forum/skins/default/templates/index_.html:27 msgid "last updated questions" msgstr "" -#: forum/skins/default/templates/index.html:27 +#: forum/skins/default/templates/index.html:28 #: forum/skins/default/templates/index_.html:27 #: forum/skins/default/templates/questions.html:47 msgid "newest" msgstr "" -#: forum/skins/default/templates/index.html:28 +#: forum/skins/default/templates/index.html:29 #: forum/skins/default/templates/index_.html:28 #: forum/skins/default/templates/questions.html:49 msgid "hottest questions" msgstr "" -#: forum/skins/default/templates/index.html:28 +#: forum/skins/default/templates/index.html:29 #: forum/skins/default/templates/index_.html:28 #: forum/skins/default/templates/questions.html:49 msgid "hottest" msgstr "" -#: forum/skins/default/templates/index.html:29 +#: forum/skins/default/templates/index.html:30 #: forum/skins/default/templates/index_.html:29 #: forum/skins/default/templates/questions.html:50 msgid "most voted questions" msgstr "" -#: forum/skins/default/templates/index.html:29 +#: forum/skins/default/templates/index.html:30 #: forum/skins/default/templates/index_.html:29 #: forum/skins/default/templates/questions.html:50 msgid "most voted" msgstr "" -#: forum/skins/default/templates/index.html:30 +#: forum/skins/default/templates/index.html:31 #: forum/skins/default/templates/index_.html:30 msgid "all questions" msgstr "" -#: forum/skins/default/templates/index.html:42 -#: forum/skins/default/templates/index_.html:42 -#: forum/skins/default/templates/users_questions.html:34 -msgid "this answer has been accepted to be correct" -msgstr "" - -#: forum/skins/default/templates/index.html:44 -#: forum/skins/default/templates/index_.html:44 -#: forum/skins/default/templates/question_summary_list_roll.html:13 -#: forum/skins/default/templates/questions.html:79 -#: forum/skins/default/templates/users_questions.html:36 -msgid "answers" -msgstr "" - -#: forum/skins/default/templates/index.html:63 -#: forum/skins/default/templates/index_.html:63 -#: forum/skins/default/templates/question.html:476 -#: forum/skins/default/templates/question_summary_list_roll.html:52 -#: forum/skins/default/templates/questions.html:136 -#: forum/skins/default/templates/tags.html:49 -#: forum/skins/default/templates/users_questions.html:52 -msgid "see questions tagged" -msgstr "" - -#: forum/skins/default/templates/index.html:74 +#: forum/skins/default/templates/index.html:41 #: forum/skins/default/templates/index_.html:74 msgid "welcome to website" msgstr "Welcome to Q&A forum" -#: forum/skins/default/templates/index.html:85 +#: forum/skins/default/templates/index.html:52 #: forum/skins/default/templates/index_.html:85 msgid "Recent tags" msgstr "" -#: forum/skins/default/templates/index.html:90 +#: forum/skins/default/templates/index.html:58 #: forum/skins/default/templates/index_.html:90 -#: forum/skins/default/templates/question.html:133 +#: forum/skins/default/templates/question.html:134 #, python-format msgid "see questions tagged '%(tagname)s'" msgstr "" -#: forum/skins/default/templates/index.html:93 -#: forum/skins/default/templates/index.html:121 +#: forum/skins/default/templates/index.html:62 +#: forum/skins/default/templates/index.html:92 #: forum/skins/default/templates/index_.html:93 #: forum/skins/default/templates/index_.html:121 msgid "popular tags" msgstr "tags" -#: forum/skins/default/templates/index.html:98 +#: forum/skins/default/templates/index.html:67 #: forum/skins/default/templates/index_.html:98 msgid "Recent awards" msgstr "Recent badges" -#: forum/skins/default/templates/index.html:104 -msgid "given to" -msgstr "" - -#: forum/skins/default/templates/index.html:109 +#: forum/skins/default/templates/index.html:80 #: forum/skins/default/templates/index_.html:109 msgid "all awards" msgstr "all badges" -#: forum/skins/default/templates/index.html:114 +#: forum/skins/default/templates/index.html:85 #: forum/skins/default/templates/index_.html:114 msgid "subscribe to last 30 questions by RSS" msgstr "" -#: forum/skins/default/templates/index.html:121 +#: forum/skins/default/templates/index.html:92 #: forum/skins/default/templates/index_.html:121 msgid "Still looking for more? See" msgstr "" -#: forum/skins/default/templates/index.html:121 +#: forum/skins/default/templates/index.html:92 #: forum/skins/default/templates/index_.html:121 msgid "complete list of questions" msgstr "list of all questions" -#: forum/skins/default/templates/index.html:121 +#: forum/skins/default/templates/index.html:92 #: forum/skins/default/templates/index_.html:121 +#: forum/skins/default/templates/auth/signup.html:28 #: forum/skins/default/templates/authopenid/signup.html:28 msgid "or" msgstr "" -#: forum/skins/default/templates/index.html:121 +#: forum/skins/default/templates/index.html:92 #: forum/skins/default/templates/index_.html:121 msgid "Please help us answer" msgstr "" -#: forum/skins/default/templates/index.html:121 +#: forum/skins/default/templates/index.html:92 #: forum/skins/default/templates/index_.html:121 msgid "list of unanswered questions" msgstr "unanswered questions" +#: forum/skins/default/templates/index_.html:42 +#: forum/skins/default/templates/question_list.html:16 +#: forum/skins/default/templates/users_questions.html:34 +msgid "this answer has been accepted to be correct" +msgstr "" + +#: forum/skins/default/templates/index_.html:44 +#: forum/skins/default/templates/question_list.html:18 +#: forum/skins/default/templates/question_summary_list_roll.html:13 +#: forum/skins/default/templates/questions.html:79 +#: forum/skins/default/templates/users_questions.html:36 +msgid "answers" +msgstr "" + +#: forum/skins/default/templates/index_.html:63 +#: forum/skins/default/templates/question.html:478 +#: forum/skins/default/templates/question_list.html:36 +#: forum/skins/default/templates/question_summary_list_roll.html:52 +#: forum/skins/default/templates/questions.html:136 +#: forum/skins/default/templates/tags.html:49 +#: forum/skins/default/templates/users_questions.html:52 +msgid "see questions tagged" +msgstr "" + #: forum/skins/default/templates/logout.html:6 #: forum/skins/default/templates/logout.html:16 msgid "Logout" @@ -2051,78 +2044,78 @@ msgstr "" "Whenever such changes occur, users will be notified via the internal " "messaging system. " -#: forum/skins/default/templates/question.html:77 #: forum/skins/default/templates/question.html:78 -#: forum/skins/default/templates/question.html:94 -#: forum/skins/default/templates/question.html:96 +#: forum/skins/default/templates/question.html:79 +#: forum/skins/default/templates/question.html:95 +#: forum/skins/default/templates/question.html:97 msgid "i like this post (click again to cancel)" msgstr "" -#: forum/skins/default/templates/question.html:80 -#: forum/skins/default/templates/question.html:98 -#: forum/skins/default/templates/question.html:255 +#: forum/skins/default/templates/question.html:81 +#: forum/skins/default/templates/question.html:99 +#: forum/skins/default/templates/question.html:256 msgid "current number of votes" msgstr "" -#: forum/skins/default/templates/question.html:89 #: forum/skins/default/templates/question.html:90 -#: forum/skins/default/templates/question.html:103 +#: forum/skins/default/templates/question.html:91 #: forum/skins/default/templates/question.html:104 +#: forum/skins/default/templates/question.html:105 msgid "i dont like this post (click again to cancel)" msgstr "" -#: forum/skins/default/templates/question.html:108 #: forum/skins/default/templates/question.html:109 +#: forum/skins/default/templates/question.html:110 msgid "mark this question as favorite (click again to cancel)" msgstr "" -#: forum/skins/default/templates/question.html:115 #: forum/skins/default/templates/question.html:116 +#: forum/skins/default/templates/question.html:117 msgid "remove favorite mark from this question (click again to restore mark)" msgstr "" -#: forum/skins/default/templates/question.html:138 -#: forum/skins/default/templates/question.html:292 +#: forum/skins/default/templates/question.html:139 +#: forum/skins/default/templates/question.html:293 #: forum/skins/default/templates/revisions_answer.html:58 #: forum/skins/default/templates/revisions_question.html:58 msgid "edit" msgstr "" -#: forum/skins/default/templates/question.html:143 +#: forum/skins/default/templates/question.html:144 msgid "reopen" msgstr "" -#: forum/skins/default/templates/question.html:147 +#: forum/skins/default/templates/question.html:148 msgid "close" msgstr "" -#: forum/skins/default/templates/question.html:153 -#: forum/skins/default/templates/question.html:297 +#: forum/skins/default/templates/question.html:154 +#: forum/skins/default/templates/question.html:298 msgid "" "report as offensive (i.e containing spam, advertising, malicious text, etc.)" msgstr "" -#: forum/skins/default/templates/question.html:154 -#: forum/skins/default/templates/question.html:298 +#: forum/skins/default/templates/question.html:155 +#: forum/skins/default/templates/question.html:299 msgid "flag offensive" msgstr "" -#: forum/skins/default/templates/question.html:162 -#: forum/skins/default/templates/question.html:309 +#: forum/skins/default/templates/question.html:163 +#: forum/skins/default/templates/question.html:310 msgid "delete" msgstr "" -#: forum/skins/default/templates/question.html:180 -#: forum/skins/default/templates/question.html:329 +#: forum/skins/default/templates/question.html:181 +#: forum/skins/default/templates/question.html:330 msgid "delete this comment" msgstr "" -#: forum/skins/default/templates/question.html:191 -#: forum/skins/default/templates/question.html:340 +#: forum/skins/default/templates/question.html:192 +#: forum/skins/default/templates/question.html:341 msgid "add comment" msgstr "post a comment" -#: forum/skins/default/templates/question.html:195 +#: forum/skins/default/templates/question.html:196 #, python-format msgid "" "\n" @@ -2136,7 +2129,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: forum/skins/default/templates/question.html:201 +#: forum/skins/default/templates/question.html:202 #, python-format msgid "" "\n" @@ -2151,18 +2144,18 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: forum/skins/default/templates/question.html:217 +#: forum/skins/default/templates/question.html:218 #, python-format msgid "" "The question has been closed for the following reason \"%(close_reason)s\" by" msgstr "" -#: forum/skins/default/templates/question.html:219 +#: forum/skins/default/templates/question.html:220 #, python-format msgid "close date %(closed_at)s" msgstr "" -#: forum/skins/default/templates/question.html:227 +#: forum/skins/default/templates/question.html:228 #, python-format msgid "" "\n" @@ -2175,63 +2168,63 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: forum/skins/default/templates/question.html:235 +#: forum/skins/default/templates/question.html:236 msgid "oldest answers will be shown first" msgstr "" -#: forum/skins/default/templates/question.html:235 +#: forum/skins/default/templates/question.html:236 msgid "oldest answers" msgstr "oldest" -#: forum/skins/default/templates/question.html:237 +#: forum/skins/default/templates/question.html:238 msgid "newest answers will be shown first" msgstr "" -#: forum/skins/default/templates/question.html:237 +#: forum/skins/default/templates/question.html:238 msgid "newest answers" msgstr "newest" -#: forum/skins/default/templates/question.html:239 +#: forum/skins/default/templates/question.html:240 msgid "most voted answers will be shown first" msgstr "" -#: forum/skins/default/templates/question.html:239 +#: forum/skins/default/templates/question.html:240 msgid "popular answers" msgstr "most voted" -#: forum/skins/default/templates/question.html:253 #: forum/skins/default/templates/question.html:254 +#: forum/skins/default/templates/question.html:255 msgid "i like this answer (click again to cancel)" msgstr "" -#: forum/skins/default/templates/question.html:260 #: forum/skins/default/templates/question.html:261 +#: forum/skins/default/templates/question.html:262 msgid "i dont like this answer (click again to cancel)" msgstr "" -#: forum/skins/default/templates/question.html:266 #: forum/skins/default/templates/question.html:267 +#: forum/skins/default/templates/question.html:268 msgid "mark this answer as favorite (click again to undo)" msgstr "" -#: forum/skins/default/templates/question.html:272 #: forum/skins/default/templates/question.html:273 +#: forum/skins/default/templates/question.html:274 msgid "the author of the question has selected this answer as correct" msgstr "" -#: forum/skins/default/templates/question.html:286 +#: forum/skins/default/templates/question.html:287 msgid "answer permanent link" msgstr "" -#: forum/skins/default/templates/question.html:287 +#: forum/skins/default/templates/question.html:288 msgid "permanent link" msgstr "link" -#: forum/skins/default/templates/question.html:309 +#: forum/skins/default/templates/question.html:310 msgid "undelete" msgstr "" -#: forum/skins/default/templates/question.html:344 +#: forum/skins/default/templates/question.html:345 #, python-format msgid "" "\n" @@ -2246,7 +2239,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: forum/skins/default/templates/question.html:350 +#: forum/skins/default/templates/question.html:351 #, python-format msgid "" "\n" @@ -2261,19 +2254,19 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: forum/skins/default/templates/question.html:376 -#: forum/skins/default/templates/question.html:379 +#: forum/skins/default/templates/question.html:377 +#: forum/skins/default/templates/question.html:380 msgid "Notify me once a day when there are any new answers" msgstr "" "<strong>Notify me</strong> once a day by email when there are any new " "answers or updates" -#: forum/skins/default/templates/question.html:382 +#: forum/skins/default/templates/question.html:383 msgid "Notify me weekly when there are any new answers" msgstr "" "<strong>Notify me</strong> weekly when there are any new answers or updates" -#: forum/skins/default/templates/question.html:387 +#: forum/skins/default/templates/question.html:388 #, python-format msgid "" "You can always adjust frequency of email updates from your %(profile_url)s" @@ -2281,21 +2274,21 @@ msgstr "" "(note: you can always <strong><a href='%(profile_url)s?" "sort=email_subscriptions'>change</a></strong> how often you receive updates)" -#: forum/skins/default/templates/question.html:392 +#: forum/skins/default/templates/question.html:393 msgid "once you sign in you will be able to subscribe for any updates here" msgstr "" "<span class='strong'>Here</span> (once you log in) you will be able to sign " "up for the periodic email updates about this question." -#: forum/skins/default/templates/question.html:403 +#: forum/skins/default/templates/question.html:404 msgid "Your answer" msgstr "" -#: forum/skins/default/templates/question.html:405 +#: forum/skins/default/templates/question.html:406 msgid "Be the first one to answer this question!" msgstr "" -#: forum/skins/default/templates/question.html:411 +#: forum/skins/default/templates/question.html:412 msgid "you can answer anonymously and then login" msgstr "" "<span class='strong big'>Please start posting your answer anonymously</span> " @@ -2304,7 +2297,7 @@ msgstr "" "answer</strong>, for discussions, <strong>please use comments</strong> and " "<strong>please do remember to vote</strong> (after you log in)!" -#: forum/skins/default/templates/question.html:415 +#: forum/skins/default/templates/question.html:416 msgid "answer your own question only to give an answer" msgstr "" "<span class='big strong'>You are welcome to answer your own question</span>, " @@ -2314,7 +2307,7 @@ msgstr "" "forget to vote :)</strong> for the answers that you liked (or perhaps did " "not like)! " -#: forum/skins/default/templates/question.html:417 +#: forum/skins/default/templates/question.html:418 msgid "please only give an answer, no discussions" msgstr "" "<span class='big strong'>Please try to give a substantial answer</span>. If " @@ -2324,39 +2317,39 @@ msgstr "" "please <strong>don't forget to vote</strong> - it really helps to select the " "best questions and answers!" -#: forum/skins/default/templates/question.html:453 +#: forum/skins/default/templates/question.html:454 msgid "Login/Signup to Post Your Answer" msgstr "" -#: forum/skins/default/templates/question.html:456 +#: forum/skins/default/templates/question.html:457 msgid "Answer Your Own Question" msgstr "" -#: forum/skins/default/templates/question.html:458 +#: forum/skins/default/templates/question.html:459 msgid "Answer the question" msgstr "Post Your Answer" -#: forum/skins/default/templates/question.html:471 +#: forum/skins/default/templates/question.html:473 msgid "Question tags" msgstr "Tags" -#: forum/skins/default/templates/question.html:481 +#: forum/skins/default/templates/question.html:483 msgid "question asked" msgstr "Asked" -#: forum/skins/default/templates/question.html:484 +#: forum/skins/default/templates/question.html:486 msgid "question was seen" msgstr "Seen" -#: forum/skins/default/templates/question.html:484 +#: forum/skins/default/templates/question.html:486 msgid "times" msgstr "" -#: forum/skins/default/templates/question.html:487 +#: forum/skins/default/templates/question.html:489 msgid "last updated" msgstr "Last updated" -#: forum/skins/default/templates/question.html:492 +#: forum/skins/default/templates/question.html:495 msgid "Related questions" msgstr "" @@ -2504,10 +2497,8 @@ msgstr[1] "" #, python-format msgid " have total %(q_num)s questions " msgid_plural " have total %(q_num)s questions " -msgstr[0] "" -"<div class=\"questions-count\">%(q_num)s</div><p>question</p>" -msgstr[1] "" -"<div class=\"questions-count\">%(q_num)s</div><p>questions</p>" +msgstr[0] "<div class=\"questions-count\">%(q_num)s</div><p>question</p>" +msgstr[1] "<div class=\"questions-count\">%(q_num)s</div><p>questions</p>" #: forum/skins/default/templates/questions.html:181 msgid "latest questions info" @@ -2613,7 +2604,7 @@ msgid "remove '%(tag_name)s' from the list of ignored tags" msgstr "" #: forum/skins/default/templates/tag_selector.html:40 -msgid "keep ingored questions hidden" +msgid "keep ignored questions hidden" msgstr "" #: forum/skins/default/templates/tags.html:6 @@ -2698,7 +2689,7 @@ msgid "Stop sending email" msgstr "Stop Email" #: forum/skins/default/templates/user_info.html:22 -#: forum/skins/default/templates/users.html:26 forum/views/users.py:907 +#: forum/skins/default/templates/users.html:26 forum/views/users.py:916 msgid "reputation" msgstr "karma" @@ -2863,19 +2854,19 @@ msgstr[1] "" msgid "User profile" msgstr "" -#: forum/skins/default/templates/user_tabs.html:7 forum/views/users.py:881 +#: forum/skins/default/templates/user_tabs.html:7 forum/views/users.py:890 msgid "overview" msgstr "" -#: forum/skins/default/templates/user_tabs.html:9 forum/views/users.py:889 +#: forum/skins/default/templates/user_tabs.html:9 forum/views/users.py:898 msgid "recent activity" msgstr "" -#: forum/skins/default/templates/user_tabs.html:12 forum/views/users.py:899 +#: forum/skins/default/templates/user_tabs.html:12 forum/views/users.py:908 msgid "comments and answers to others questions" msgstr "" -#: forum/skins/default/templates/user_tabs.html:13 forum/views/users.py:898 +#: forum/skins/default/templates/user_tabs.html:13 forum/views/users.py:907 msgid "responses" msgstr "" @@ -2887,11 +2878,11 @@ msgstr "Graph of user karma" msgid "reputation history" msgstr "karma history" -#: forum/skins/default/templates/user_tabs.html:20 forum/views/users.py:925 +#: forum/skins/default/templates/user_tabs.html:20 forum/views/users.py:934 msgid "user vote record" msgstr "" -#: forum/skins/default/templates/user_tabs.html:20 forum/views/users.py:924 +#: forum/skins/default/templates/user_tabs.html:20 forum/views/users.py:933 msgid "casted votes" msgstr "votes" @@ -2903,11 +2894,11 @@ msgstr "" msgid "favorites" msgstr "" -#: forum/skins/default/templates/user_tabs.html:27 forum/views/users.py:934 +#: forum/skins/default/templates/user_tabs.html:27 forum/views/users.py:943 msgid "email subscription settings" msgstr "" -#: forum/skins/default/templates/user_tabs.html:28 forum/views/users.py:933 +#: forum/skins/default/templates/user_tabs.html:28 forum/views/users.py:942 msgid "email subscriptions" msgstr "" @@ -2949,6 +2940,323 @@ msgstr "" msgid "thumb-up off" msgstr "" +#: forum/skins/default/templates/auth/auth_settings.html:5 +#: forum/skins/default/templates/auth/auth_settings.html:7 +msgid "Authentication settings" +msgstr "" + +#: forum/skins/default/templates/auth/auth_settings.html:9 +msgid "" +"These are the external authentication providers currently associated with " +"your account." +msgstr "" + +#: forum/skins/default/templates/auth/auth_settings.html:12 +msgid "remove" +msgstr "" + +#: forum/skins/default/templates/auth/auth_settings.html:17 +msgid "" +"You currently have no external authentication provider associated with your " +"account." +msgstr "" + +#: forum/skins/default/templates/auth/auth_settings.html:19 +msgid "Add new provider" +msgstr "" + +#: forum/skins/default/templates/auth/auth_settings.html:21 +#: forum/skins/default/templates/authopenid/changepw.html:8 +msgid "This is where you can change your password. Make sure you remember it!" +msgstr "" +"<span class='strong'>To change your password</span> please fill out and " +"submit this form" + +#: forum/skins/default/templates/auth/auth_settings.html:24 +msgid "" +"You can set up a password for your account, so you can login using standard " +"username and password!" +msgstr "" + +#: forum/skins/default/templates/auth/auth_settings.html:31 +msgid "Create password" +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:5 +#: forum/skins/default/templates/authopenid/complete.html:19 +msgid "Connect your OpenID with this site" +msgstr "New user signup" + +#: forum/skins/default/templates/auth/complete.html:8 +#: forum/skins/default/templates/authopenid/complete.html:22 +msgid "Connect your OpenID with your account on this site" +msgstr "New user signup" + +#: forum/skins/default/templates/auth/complete.html:12 +msgid "You are here for the first time with " +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:13 +msgid "" +"Please create your screen name and save your email address. Saved email " +"address will let you subscribe for the updates on the most interesting " +"questions and will be used to create and retrieve your unique avatar image. " +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:15 +#: forum/skins/default/templates/authopenid/complete.html:42 +msgid "This account already exists, please use another." +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:30 +#: forum/skins/default/templates/authopenid/complete.html:57 +msgid "Sorry, looks like we have some errors:" +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:47 +#: forum/skins/default/templates/authopenid/complete.html:82 +msgid "Screen name label" +msgstr "<strong>Screen Name</strong> (<i>will be shown to others</i>)" + +#: forum/skins/default/templates/auth/complete.html:54 +#: forum/skins/default/templates/authopenid/complete.html:89 +msgid "Email address label" +msgstr "" +"<strong>Email Address</strong> (<i>will <strong>not</strong> be shared with " +"anyone, must be valid</i>)" + +#: forum/skins/default/templates/auth/complete.html:60 +#: forum/skins/default/templates/auth/signup.html:18 +#: forum/skins/default/templates/authopenid/complete.html:95 +#: forum/skins/default/templates/authopenid/signup.html:18 +msgid "receive updates motivational blurb" +msgstr "" +"<strong>Receive forum updates by email</strong> - this will help our " +"community grow and become more useful.<br/>By default <span " +"class='orange'>Q&A</span> forum sends up to <strong>one email digest per " +"week</strong> - only when there is anything new.<br/>If you like, please " +"adjust this now or any time later from your user account." + +#: forum/skins/default/templates/auth/complete.html:64 +#: forum/skins/default/templates/auth/signup.html:22 +#: forum/skins/default/templates/authopenid/complete.html:99 +#: forum/skins/default/templates/authopenid/signup.html:22 +msgid "please select one of the options above" +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:67 +#: forum/skins/default/templates/authopenid/complete.html:102 +msgid "Tag filter tool will be your right panel, once you log in." +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:68 +#: forum/skins/default/templates/authopenid/complete.html:103 +msgid "create account" +msgstr "Signup" + +#: forum/skins/default/templates/auth/complete.html:77 +#: forum/skins/default/templates/authopenid/complete.html:112 +msgid "Existing account" +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:78 +#: forum/skins/default/templates/authopenid/complete.html:113 +msgid "user name" +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:79 +#: forum/skins/default/templates/authopenid/complete.html:114 +msgid "password" +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:86 +#: forum/skins/default/templates/authopenid/complete.html:121 +msgid "Register" +msgstr "" + +#: forum/skins/default/templates/auth/complete.html:87 +#: forum/skins/default/templates/authopenid/complete.html:122 +#: forum/skins/default/templates/authopenid/signin.html:164 +msgid "Forgot your password?" +msgstr "" + +#: forum/skins/default/templates/auth/email_validation.html:6 +#: forum/skins/default/templates/auth/temp_login_email.html:6 +#: forum/skins/default/templates/authopenid/email_validation.txt:2 +msgid "Greetings from the Q&A forum" +msgstr "" + +#: forum/skins/default/templates/auth/email_validation.html:8 +#: forum/skins/default/templates/authopenid/email_validation.txt:4 +msgid "To make use of the Forum, please follow the link below:" +msgstr "" + +#: forum/skins/default/templates/auth/email_validation.html:12 +#: forum/skins/default/templates/authopenid/email_validation.txt:8 +msgid "Following the link above will help us verify your email address." +msgstr "" + +#: forum/skins/default/templates/auth/email_validation.html:14 +#: forum/skins/default/templates/auth/temp_login_email.html:14 +msgid "" +"If you beleive that this message was sent in mistake -\n" +" no further action is needed. Just ingore this email, we apologize\n" +" for any inconvenience" +msgstr "" + +#: forum/skins/default/templates/auth/email_validation.html:18 +#: forum/skins/default/templates/auth/temp_login_email.html:18 +msgid "" +"Sincerely,<br />\n" +" Forum Administrator" +msgstr "" +"Sincerely,\n" +"Q&A Forum Administrator" + +#: forum/skins/default/templates/auth/signin.html:22 +#: forum/skins/default/templates/authopenid/signin.html:5 +#: forum/skins/default/templates/authopenid/signin.html:21 +msgid "User login" +msgstr "User login" + +#: forum/skins/default/templates/auth/signin.html:29 +#: forum/skins/default/templates/authopenid/signin.html:42 +msgid "Click to sign in through any of these services." +msgstr "" +"<p><span class=\"big strong\">Please select your favorite login method below." +"</span></p><p><font color=\"gray\">External login services use <a href=" +"\"http://openid.net\"><b>OpenID</b></a> technology, where your password " +"always stays confidential between you and your login provider and you don't " +"have to remember another one.</font></p>" + +#: forum/skins/default/templates/auth/signin.html:34 +msgid "" +"Take the oppurtunity to validate my email next to the external provider I " +"choose." +msgstr "" + +#: forum/skins/default/templates/auth/signin.html:76 +#: forum/skins/default/templates/auth/signin.html:82 +msgid "Or..." +msgstr "" + +#: forum/skins/default/templates/auth/signin.html:84 +msgid "Click" +msgstr "" + +#: forum/skins/default/templates/auth/signin.html:84 +msgid "if you're having troubles signing in." +msgstr "" + +#: forum/skins/default/templates/auth/signin.html:88 +msgid "Enter your " +msgstr "" + +#: forum/skins/default/templates/auth/signin.html:144 +#: forum/skins/default/templates/authopenid/signin.html:174 +msgid "Why use OpenID?" +msgstr "" + +#: forum/skins/default/templates/auth/signin.html:147 +#: forum/skins/default/templates/authopenid/signin.html:177 +msgid "with openid it is easier" +msgstr "With the OpenID you don't need to create new username and password." + +#: forum/skins/default/templates/auth/signin.html:150 +#: forum/skins/default/templates/authopenid/signin.html:180 +msgid "reuse openid" +msgstr "You can safely re-use the same login for all OpenID-enabled websites." + +#: forum/skins/default/templates/auth/signin.html:153 +#: forum/skins/default/templates/authopenid/signin.html:183 +msgid "openid is widely adopted" +msgstr "" +"There are > 160,000,000 OpenID account in use. Over 10,000 sites are OpenID-" +"enabled." + +#: forum/skins/default/templates/auth/signin.html:156 +#: forum/skins/default/templates/authopenid/signin.html:186 +msgid "openid is supported open standard" +msgstr "OpenID is based on an open standard, supported by many organizations." + +#: forum/skins/default/templates/auth/signin.html:161 +#: forum/skins/default/templates/authopenid/signin.html:191 +msgid "Find out more" +msgstr "" + +#: forum/skins/default/templates/auth/signin.html:162 +#: forum/skins/default/templates/authopenid/signin.html:192 +msgid "Get OpenID" +msgstr "" + +#: forum/skins/default/templates/auth/signup.html:4 +#: forum/skins/default/templates/authopenid/signup.html:4 +msgid "Signup" +msgstr "" + +#: forum/skins/default/templates/auth/signup.html:8 +#: forum/skins/default/templates/authopenid/signup.html:8 +msgid "Create login name and password" +msgstr "" + +#: forum/skins/default/templates/auth/signup.html:10 +#: forum/skins/default/templates/authopenid/signup.html:10 +msgid "Traditional signup info" +msgstr "" +"<span class='strong big'>If you prefer, create your forum login name and " +"password here. However</span>, please keep in mind that we also support " +"<strong>OpenID</strong> login method. With <strong>OpenID</strong> you can " +"simply reuse your external login (e.g. Gmail or AOL) without ever sharing " +"your login details with anyone and having to remember yet another password." + +#: forum/skins/default/templates/auth/signup.html:25 +#: forum/skins/default/templates/authopenid/signup.html:25 +msgid "" +"Please read and type in the two words below to help us prevent automated " +"account creation." +msgstr "" + +#: forum/skins/default/templates/auth/signup.html:27 +#: forum/skins/default/templates/authopenid/signup.html:27 +msgid "Create Account" +msgstr "" + +#: forum/skins/default/templates/auth/signup.html:29 +msgid "return to login page" +msgstr "" + +#: forum/skins/default/templates/auth/temp_login_email.html:8 +msgid "You're seeing this because someone requested a temporary login link" +msgstr "" + +#: forum/skins/default/templates/auth/temp_login_email.html:12 +msgid "Following the link above will give you access to your account." +msgstr "" + +#: forum/skins/default/templates/auth/temp_login_request.html:5 +msgid "Request temporary login key" +msgstr "" + +#: forum/skins/default/templates/auth/temp_login_request.html:7 +msgid "Account: request temporary login key" +msgstr "" + +#: forum/skins/default/templates/auth/temp_login_request.html:8 +msgid "" +"\n" +" If you're experiencing problems accessing your account, or if you forgot " +"your password,\n" +" here you can request a temporary login key. Fill out your account email " +"and we'll send you a temporary access link that\n" +" will enable you to access your account. This token is valid only once " +"and for a limited period of time.\n" +" " +msgstr "" + +#: forum/skins/default/templates/auth/temp_login_request.html:25 +msgid "Send link" +msgstr "" + #: forum/skins/default/templates/authopenid/changeemail.html:3 #: forum/skins/default/templates/authopenid/changeemail.html:9 #: forum/skins/default/templates/authopenid/changeemail.html:38 @@ -3059,12 +3367,6 @@ msgstr "" "validated before</span> so the new key was not sent. You can <a href='%" "(change_link)s'>change</a> email used for update subscriptions if necessary." -#: forum/skins/default/templates/authopenid/changeopenid.html:4 -#: forum/skins/default/templates/authopenid/changeopenid.html:30 -#: forum/skins/default/templates/authopenid/settings.html:34 -msgid "Change OpenID" -msgstr "" - #: forum/skins/default/templates/authopenid/changeopenid.html:8 msgid "Account: change OpenID URL" msgstr "" @@ -3084,30 +3386,10 @@ msgstr "" msgid "OpenID URL:" msgstr "" -#: forum/skins/default/templates/authopenid/changepw.html:5 -#: forum/skins/default/templates/authopenid/changepw.html:14 -#: forum/skins/default/templates/authopenid/settings.html:29 -msgid "Change password" -msgstr "" - #: forum/skins/default/templates/authopenid/changepw.html:7 msgid "Account: change password" msgstr "Change your password" -#: forum/skins/default/templates/authopenid/changepw.html:8 -msgid "This is where you can change your password. Make sure you remember it!" -msgstr "" -"<span class='strong'>To change your password</span> please fill out and " -"submit this form" - -#: forum/skins/default/templates/authopenid/complete.html:19 -msgid "Connect your OpenID with this site" -msgstr "New user signup" - -#: forum/skins/default/templates/authopenid/complete.html:22 -msgid "Connect your OpenID with your account on this site" -msgstr "New user signup" - #: forum/skins/default/templates/authopenid/complete.html:27 #, python-format msgid "register new %(provider)s account info, see %(gravatar_faq_url)s" @@ -3161,68 +3443,6 @@ msgstr "" "and will be used to create and retrieve your unique avatar image - <a href='%" "(gravatar_faq_url)s'><strong>gravatar</strong></a>.</p>" -#: forum/skins/default/templates/authopenid/complete.html:42 -msgid "This account already exists, please use another." -msgstr "" - -#: forum/skins/default/templates/authopenid/complete.html:57 -msgid "Sorry, looks like we have some errors:" -msgstr "" - -#: forum/skins/default/templates/authopenid/complete.html:82 -msgid "Screen name label" -msgstr "<strong>Screen Name</strong> (<i>will be shown to others</i>)" - -#: forum/skins/default/templates/authopenid/complete.html:89 -msgid "Email address label" -msgstr "" -"<strong>Email Address</strong> (<i>will <strong>not</strong> be shared with " -"anyone, must be valid</i>)" - -#: forum/skins/default/templates/authopenid/complete.html:95 -#: forum/skins/default/templates/authopenid/signup.html:18 -msgid "receive updates motivational blurb" -msgstr "" -"<strong>Receive forum updates by email</strong> - this will help our " -"community grow and become more useful.<br/>By default <span " -"class='orange'>Q&A</span> forum sends up to <strong>one email digest per " -"week</strong> - only when there is anything new.<br/>If you like, please " -"adjust this now or any time later from your user account." - -#: forum/skins/default/templates/authopenid/complete.html:99 -#: forum/skins/default/templates/authopenid/signup.html:22 -msgid "please select one of the options above" -msgstr "" - -#: forum/skins/default/templates/authopenid/complete.html:102 -msgid "Tag filter tool will be your right panel, once you log in." -msgstr "" - -#: forum/skins/default/templates/authopenid/complete.html:103 -msgid "create account" -msgstr "Signup" - -#: forum/skins/default/templates/authopenid/complete.html:112 -msgid "Existing account" -msgstr "" - -#: forum/skins/default/templates/authopenid/complete.html:113 -msgid "user name" -msgstr "" - -#: forum/skins/default/templates/authopenid/complete.html:114 -msgid "password" -msgstr "" - -#: forum/skins/default/templates/authopenid/complete.html:121 -msgid "Register" -msgstr "" - -#: forum/skins/default/templates/authopenid/complete.html:122 -#: forum/skins/default/templates/authopenid/signin.html:164 -msgid "Forgot your password?" -msgstr "" - #: forum/skins/default/templates/authopenid/confirm_email.txt:2 msgid "Thank you for registering at our Q&A forum!" msgstr "" @@ -3254,11 +3474,6 @@ msgstr "" "Sincerely,\n" "Q&A Forum Administrator" -#: forum/skins/default/templates/authopenid/delete.html:4 -#: forum/skins/default/templates/authopenid/settings.html:38 -msgid "Delete account" -msgstr "" - #: forum/skins/default/templates/authopenid/delete.html:8 msgid "Account: delete account" msgstr "" @@ -3289,18 +3504,6 @@ msgstr "" msgid "Delete account permanently" msgstr "" -#: forum/skins/default/templates/authopenid/email_validation.txt:2 -msgid "Greetings from the Q&A forum" -msgstr "" - -#: forum/skins/default/templates/authopenid/email_validation.txt:4 -msgid "To make use of the Forum, please follow the link below:" -msgstr "" - -#: forum/skins/default/templates/authopenid/email_validation.txt:8 -msgid "Following the link above will help us verify your email address." -msgstr "" - #: forum/skins/default/templates/authopenid/email_validation.txt:10 msgid "" "If you beleive that this message was sent in mistake - \n" @@ -3361,35 +3564,6 @@ msgstr "" "* login with user name %(username)s and password %(password)s\n" "* go to your user profile and set the password to something you can remember" -#: forum/skins/default/templates/authopenid/settings.html:4 -msgid "Account functions" -msgstr "" - -#: forum/skins/default/templates/authopenid/settings.html:30 -msgid "Give your account a new password." -msgstr "" - -#: forum/skins/default/templates/authopenid/settings.html:31 -msgid "Change email " -msgstr "" - -#: forum/skins/default/templates/authopenid/settings.html:32 -msgid "Add or update the email address associated with your account." -msgstr "" - -#: forum/skins/default/templates/authopenid/settings.html:35 -msgid "Change openid associated to your account" -msgstr "" - -#: forum/skins/default/templates/authopenid/settings.html:39 -msgid "Erase your username and all your data from website" -msgstr "" - -#: forum/skins/default/templates/authopenid/signin.html:5 -#: forum/skins/default/templates/authopenid/signin.html:21 -msgid "User login" -msgstr "User login" - #: forum/skins/default/templates/authopenid/signin.html:28 #, python-format msgid "" @@ -3414,15 +3588,6 @@ msgstr "" "strong> %(summary)s...\"</i> <span class=\"strong big\">is saved and will be " "posted once you log in.</span>" -#: forum/skins/default/templates/authopenid/signin.html:42 -msgid "Click to sign in through any of these services." -msgstr "" -"<p><span class=\"big strong\">Please select your favorite login method below." -"</span></p><p><font color=\"gray\">External login services use <a href=" -"\"http://openid.net\"><b>OpenID</b></a> technology, where your password " -"always stays confidential between you and your login provider and you don't " -"have to remember another one.</font></p>" - # msgid "Click to sign in through any of these services." # msgstr "" # "<p><span class=\"big strong\">Please select your favorite login method below." @@ -3471,63 +3636,6 @@ msgstr "" msgid "Create account" msgstr "" -#: forum/skins/default/templates/authopenid/signin.html:174 -msgid "Why use OpenID?" -msgstr "" - -#: forum/skins/default/templates/authopenid/signin.html:177 -msgid "with openid it is easier" -msgstr "With the OpenID you don't need to create new username and password." - -#: forum/skins/default/templates/authopenid/signin.html:180 -msgid "reuse openid" -msgstr "You can safely re-use the same login for all OpenID-enabled websites." - -#: forum/skins/default/templates/authopenid/signin.html:183 -msgid "openid is widely adopted" -msgstr "" -"There are > 160,000,000 OpenID account in use. Over 10,000 sites are OpenID-" -"enabled." - -#: forum/skins/default/templates/authopenid/signin.html:186 -msgid "openid is supported open standard" -msgstr "OpenID is based on an open standard, supported by many organizations." - -#: forum/skins/default/templates/authopenid/signin.html:191 -msgid "Find out more" -msgstr "" - -#: forum/skins/default/templates/authopenid/signin.html:192 -msgid "Get OpenID" -msgstr "" - -#: forum/skins/default/templates/authopenid/signup.html:4 -msgid "Signup" -msgstr "" - -#: forum/skins/default/templates/authopenid/signup.html:8 -msgid "Create login name and password" -msgstr "" - -#: forum/skins/default/templates/authopenid/signup.html:10 -msgid "Traditional signup info" -msgstr "" -"<span class='strong big'>If you prefer, create your forum login name and " -"password here. However</span>, please keep in mind that we also support " -"<strong>OpenID</strong> login method. With <strong>OpenID</strong> you can " -"simply reuse your external login (e.g. Gmail or AOL) without ever sharing " -"your login details with anyone and having to remember yet another password." - -#: forum/skins/default/templates/authopenid/signup.html:25 -msgid "" -"Please read and type in the two words below to help us prevent automated " -"account creation." -msgstr "" - -#: forum/skins/default/templates/authopenid/signup.html:27 -msgid "Create Account" -msgstr "" - #: forum/skins/default/templates/authopenid/signup.html:29 msgid "return to OpenID login" msgstr "" @@ -3537,26 +3645,26 @@ msgstr "" msgid "Connect to %(APP_SHORT_NAME)s with Facebook!" msgstr "" -#: forum/templatetags/extra_tags.py:166 forum/templatetags/extra_tags.py:193 +#: forum/templatetags/extra_tags.py:167 forum/templatetags/extra_tags.py:194 msgid "reputation points" msgstr "karma" -#: forum/templatetags/extra_tags.py:253 +#: forum/templatetags/extra_tags.py:254 msgid "2 days ago" msgstr "" -#: forum/templatetags/extra_tags.py:255 +#: forum/templatetags/extra_tags.py:256 msgid "yesterday" msgstr "" -#: forum/templatetags/extra_tags.py:257 +#: forum/templatetags/extra_tags.py:258 #, python-format msgid "%(hr)d hour ago" msgid_plural "%(hr)d hours ago" msgstr[0] "" msgstr[1] "" -#: forum/templatetags/extra_tags.py:259 +#: forum/templatetags/extra_tags.py:260 #, python-format msgid "%(min)d min ago" msgid_plural "%(min)d mins ago" @@ -3603,10 +3711,6 @@ msgstr "Your email <i>(never shared)</i>" msgid "email address is required" msgstr "" -#: forum/utils/forms.py:111 -msgid "please enter a valid email address" -msgstr "" - #: forum/utils/forms.py:112 msgid "this email is already used by someone else, please choose another" msgstr "" @@ -3631,14 +3735,77 @@ msgstr "" msgid "sorry, entered passwords did not match, please try again" msgstr "" -#: forum/views/commands.py:217 +#: forum/views/auth.py:99 forum/views/auth.py:108 +msgid "" +"Sorry, these login credentials belong to anoother user. Plese terminate your " +"current session and try again." +msgstr "" + +#: forum/views/auth.py:101 +msgid "You are already logged in with that user." +msgstr "" + +#: forum/views/auth.py:106 +msgid "These login credentials are already associated with your account." +msgstr "" + +#: forum/views/auth.py:112 +msgid "The new credentials are now associated with your account" +msgstr "" + +#: forum/views/auth.py:150 +msgid "" +"Oops, something went wrong in the middle of this process. Please try again." +msgstr "" + +#: forum/views/auth.py:210 +msgid "Temporary login link" +msgstr "" + +#: forum/views/auth.py:215 +msgid "An email has been sent with your temporary login key" +msgstr "" + +#: forum/views/auth.py:230 +msgid "" +"You are logged in with a temporary access key, please take the time to fix " +"your issue with authentication." +msgstr "" + +#: forum/views/auth.py:236 +msgid "Email Validation" +msgstr "" + +#: forum/views/auth.py:247 +msgid "Thank you, your email is now validated." +msgstr "" + +#: forum/views/auth.py:271 +msgid "Your password was changed" +msgstr "" + +#: forum/views/auth.py:273 +msgid "New password set" +msgstr "New password created" + +#: forum/views/auth.py:306 +#, python-format +msgid "You removed the association with %s" +msgstr "" + +#: forum/views/auth.py:345 +#, python-format +msgid "Welcome back %s, you are now logged in" +msgstr "" + +#: forum/views/commands.py:209 #, python-format msgid "subscription saved, %(email)s needs validation, see %(details_url)s" msgstr "" "Your subscription is saved, but email address %(email)s needs to be " "validated, please see <a href='%(details_url)s'>more details here</a>" -#: forum/views/commands.py:225 +#: forum/views/commands.py:217 msgid "email update frequency has been set to daily" msgstr "" @@ -3654,76 +3821,76 @@ msgstr "" msgid "We look forward to hearing your feedback! Please, give it next time :)" msgstr "" -#: forum/views/users.py:842 forum/views/users.py:846 +#: forum/views/users.py:851 forum/views/users.py:855 msgid "changes saved" msgstr "" -#: forum/views/users.py:852 +#: forum/views/users.py:861 msgid "email updates canceled" msgstr "" -#: forum/views/users.py:882 +#: forum/views/users.py:891 msgid "user profile" msgstr "" -#: forum/views/users.py:883 +#: forum/views/users.py:892 msgid "user profile overview" msgstr "" -#: forum/views/users.py:890 +#: forum/views/users.py:899 msgid "recent user activity" msgstr "" -#: forum/views/users.py:891 +#: forum/views/users.py:900 msgid "profile - recent activity" msgstr "" -#: forum/views/users.py:900 +#: forum/views/users.py:909 msgid "profile - responses" msgstr "" -#: forum/views/users.py:908 +#: forum/views/users.py:917 msgid "user reputation in the community" msgstr "user karma" -#: forum/views/users.py:909 +#: forum/views/users.py:918 msgid "profile - user reputation" msgstr "Profile - User's Karma" -#: forum/views/users.py:915 +#: forum/views/users.py:924 msgid "favorite questions" msgstr "" -#: forum/views/users.py:916 +#: forum/views/users.py:925 msgid "users favorite questions" msgstr "" -#: forum/views/users.py:917 +#: forum/views/users.py:926 msgid "profile - favorite questions" msgstr "" -#: forum/views/users.py:926 +#: forum/views/users.py:935 msgid "profile - votes" msgstr "" -#: forum/views/users.py:935 +#: forum/views/users.py:944 msgid "profile - email subscriptions" msgstr "" -#: forum/views/writers.py:74 +#: forum/views/writers.py:70 msgid "uploading images is limited to users with >60 reputation points" msgstr "sorry, file uploading requires karma >60" -#: forum/views/writers.py:76 +#: forum/views/writers.py:72 msgid "allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'" msgstr "" -#: forum/views/writers.py:78 +#: forum/views/writers.py:74 #, python-format msgid "maximum upload file size is %sK" msgstr "" -#: forum/views/writers.py:80 +#: forum/views/writers.py:76 #, python-format msgid "" "Error uploading file. Please contact the site administrator. Thank you. %s" @@ -3734,6 +3901,65 @@ msgstr "" msgid "books/" msgstr "" +#: forum_modules/facebookauth/authentication.py:29 +msgid "Sorry, your Facebook session has expired, please try again" +msgstr "" + +#: forum_modules/facebookauth/authentication.py:31 +msgid "" +"The authentication with Facebook connect failed due to an invalid signature" +msgstr "" + +#: forum_modules/facebookauth/authentication.py:33 +msgid "" +"The authentication with Facebook connect failed, cannot find authentication " +"tokens" +msgstr "" + +#: forum_modules/localauth/urls.py:7 +msgid "local/" +msgstr "" + +#: forum_modules/localauth/views.py:23 +msgid "A validation email has been sent to your email address. " +msgstr "" + +#: forum_modules/oauthauth/consumer.py:33 +msgid "Error, the oauth token is not on the server" +msgstr "" + +#: forum_modules/oauthauth/consumer.py:38 +msgid "Something went wrong! Auth tokens do not match" +msgstr "" + +#: forum_modules/openidauth/consumer.py:40 +msgid "Sorry, but your input is not a valid OpenId" +msgstr "" + +#: forum_modules/openidauth/consumer.py:92 +msgid "The OpenId authentication request was canceled" +msgstr "" + +#: forum_modules/openidauth/consumer.py:94 +msgid "The OpenId authentication failed: " +msgstr "" + +#: forum_modules/openidauth/consumer.py:96 +msgid "Setup needed" +msgstr "" + +#: forum_modules/openidauth/consumer.py:98 +msgid "The OpenId authentication failed with an unknown status: " +msgstr "" + +#: forum_modules/openidauth/templates/openidurl.html:7 +msgid "Enter your OpenId Url" +msgstr "" + +#: stackexchange/management/commands/load_stackexchange.py:124 +msgid "Congratulations, you are now an Administrator" +msgstr "" + #~ msgid "" #~ "\n" #~ " have total %(q_num)s unanswered questions\n" diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo Binary files differindex fc7ebe14..2b514069 100644 --- a/locale/es/LC_MESSAGES/django.mo +++ b/locale/es/LC_MESSAGES/django.mo diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index b528fcf2..83ff69bf 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -1,1179 +1,1453 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-08-12 15:53+0000\n" -"PO-Revision-Date: \n" -"Last-Translator: Bruno Sarlo <bsarlo@gmail.com>\n" +"POT-Creation-Date: 2010-02-09 20:10+0000\n" +"PO-Revision-Date: 2010-02-09 14:11-0600\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: settings.py:12 urls.py:25 forum/views.py:304 forum/views.py:698 -msgid "account/" -msgstr "cuenta/" - -#: settings.py:12 urls.py:26 django_authopenid/urls.py:9 -#: django_authopenid/urls.py:10 django_authopenid/urls.py:11 -#: django_authopenid/urls.py:13 forum/views.py:304 forum/views.py:699 -#: templates/authopenid/confirm_email.txt:10 -msgid "signin/" -msgstr "ingresar/" - -#: urls.py:22 -msgid "upfiles/" -msgstr "archivossubidos/" - -#: urls.py:27 urls.py:28 urls.py:29 django_authopenid/urls.py:26 -#: django_authopenid/urls.py:27 -msgid "email/" -msgstr "email/" - -#: urls.py:27 -msgid "change/" -msgstr "cambiar/" - -#: urls.py:28 -msgid "sendkey/" -msgstr "enviarclave/" - -#: urls.py:29 -msgid "verify/" -msgstr "verificar/" - -#: urls.py:30 -msgid "about/" -msgstr "acercadenosotros/" - -#: urls.py:31 -msgid "faq/" -msgstr "preguntasfrecuentes/" - -#: urls.py:32 -msgid "privacy/" -msgstr "códigodeprivacidad/" - -#: urls.py:33 -msgid "logout/" -msgstr "cerrarsesion/" - -#: urls.py:34 urls.py:35 urls.py:36 urls.py:48 forum/models.py:418 -msgid "answers/" -msgstr "respuestas/" - -#: urls.py:34 urls.py:46 -msgid "comments/" -msgstr "comentarios/" - -#: urls.py:35 urls.py:40 urls.py:54 templates/user_info.html:34 -msgid "edit/" -msgstr "editar/" - -#: urls.py:36 urls.py:45 -msgid "revisions/" -msgstr "revisiones/" - -#: urls.py:37 urls.py:38 urls.py:39 urls.py:40 urls.py:41 urls.py:42 -#: urls.py:43 urls.py:44 urls.py:45 urls.py:46 urls.py:47 forum/feed.py:19 -#: forum/models.py:306 forum/views.py:1416 -msgid "questions/" -msgstr "preguntas/" - -#: urls.py:38 urls.py:64 -msgid "ask/" -msgstr "preguntar/" +#: django_authopenid/forms.py:70 +msgid "choose a username" +msgstr "" -#: urls.py:39 -msgid "unanswered/" -msgstr "sinrespuesta/" +#: django_authopenid/forms.py:76 +msgid "user name is required" +msgstr "" -#: urls.py:41 -msgid "close/" -msgstr "cerrar/" +#: django_authopenid/forms.py:77 +msgid "sorry, this name is taken, please choose another" +msgstr "" -#: urls.py:42 -msgid "reopen/" -msgstr "reabrir/" +#: django_authopenid/forms.py:78 +msgid "sorry, this name is not allowed, please choose another" +msgstr "" -#: urls.py:43 -msgid "answer/" -msgstr "respuesta/" +#: django_authopenid/forms.py:79 +msgid "sorry, there is no user with this name" +msgstr "" -#: urls.py:44 -msgid "vote/" -msgstr "votar/" +#: django_authopenid/forms.py:80 +msgid "sorry, we have a serious error - user name is taken by several users" +msgstr "" -#: urls.py:47 urls.py:48 django_authopenid/urls.py:29 -msgid "delete/" -msgstr "borrar/" +#: django_authopenid/forms.py:81 +msgid "user name can only consist of letters, empty space and underscore" +msgstr "" -#: urls.py:50 -msgid "question/" -msgstr "pregunta/" +#: django_authopenid/forms.py:116 +msgid "your email address" +msgstr "" -#: urls.py:51 urls.py:52 forum/views.py:740 forum/views.py:2013 -msgid "tags/" -msgstr "etiquetas/" +#: django_authopenid/forms.py:117 +msgid "email address is required" +msgstr "" -#: urls.py:53 urls.py:54 urls.py:55 forum/views.py:993 forum/views.py:997 -#: forum/views.py:1418 forum/views.py:1751 forum/views.py:2015 -msgid "users/" -msgstr "usuarios/" +#: django_authopenid/forms.py:118 +msgid "please enter a valid email address" +msgstr "" -#: urls.py:56 urls.py:57 -msgid "badges/" -msgstr "distinciones/" +#: django_authopenid/forms.py:119 +msgid "this email is already used by someone else, please choose another" +msgstr "" -#: urls.py:58 -msgid "messages/" -msgstr "mensajes/" +#: django_authopenid/forms.py:163 django_authopenid/views.py:118 +msgid "i-names are not supported" +msgstr "" -#: urls.py:58 -msgid "markread/" -msgstr "marcarleido/" +#: django_authopenid/forms.py:219 +msgid "Account with this name already exists on the forum" +msgstr "" -#: urls.py:60 -msgid "nimda/" -msgstr "administrador/" +#: django_authopenid/forms.py:220 +msgid "can't have two logins to the same account yet, sorry." +msgstr "" -#: urls.py:62 -msgid "upload/" -msgstr "subir/" +#: django_authopenid/forms.py:242 +msgid "Please enter valid username and password (both are case-sensitive)." +msgstr "" -#: urls.py:63 urls.py:64 urls.py:65 -msgid "books/" -msgstr "libros/" +#: django_authopenid/forms.py:245 django_authopenid/forms.py:295 +msgid "This account is inactive." +msgstr "" -#: urls.py:66 -msgid "search/" -msgstr "buscar/" +#: django_authopenid/forms.py:247 +msgid "Login failed." +msgstr "" -#: django_authopenid/forms.py:67 django_authopenid/views.py:102 -msgid "i-names are not supported" -msgstr "i-names no son soportados" +#: django_authopenid/forms.py:249 +msgid "Please enter username and password" +msgstr "" -#: django_authopenid/forms.py:102 -msgid "" -"Usernames can only contain letters, numbers and " -"underscores" +#: django_authopenid/forms.py:251 +msgid "Please enter your password" msgstr "" -"Los nombres de usuario solo pueden contener letras, números y guión bajo" -#: django_authopenid/forms.py:109 -msgid "" -"This username does not exist in our database. Please " -"choose another." +#: django_authopenid/forms.py:253 +msgid "Please enter user name" msgstr "" -"Este nombre de usuario no existe en nuestra base de datos. Por favor elija " -"otro." -#: django_authopenid/forms.py:126 django_authopenid/forms.py:233 +#: django_authopenid/forms.py:291 msgid "" "Please enter a valid username and password. Note that " "both fields are case-sensitive." msgstr "" -"Por favor ingrese un usuario y contraseña validos. Ambos campos son " -"sensibles a mayúsculas y minúsculas." -#: django_authopenid/forms.py:130 django_authopenid/forms.py:237 -msgid "This account is inactive." -msgstr "Esta cuenta esta inactiva." - -#: django_authopenid/forms.py:158 django_authopenid/forms.py:210 -msgid "invalid user name" -msgstr "nombre de usuario no valido" - -#: django_authopenid/forms.py:160 -msgid "sorry, this name can not be used, please try another" +#: django_authopenid/forms.py:313 +msgid "choose password" msgstr "" -"perdón, pero este nombre de usuario no puede ser usado, intente con otro" -#: django_authopenid/forms.py:162 -msgid "username too short" -msgstr "nombre de usuario muy corto" - -#: django_authopenid/forms.py:170 django_authopenid/forms.py:171 -msgid "this name is already in use - please try anoter" -msgstr "este nombre ya está tomado - por favor intente con otro" +#: django_authopenid/forms.py:314 +msgid "password is required" +msgstr "" -#: django_authopenid/forms.py:185 -msgid "" -"This email is already registered in our database. " -"Please choose another." +#: django_authopenid/forms.py:317 +msgid "retype password" msgstr "" -"Este email ya está registrado en nuestra base de datos. Por favor, intente " -"con otro." -#: django_authopenid/forms.py:216 -msgid "" -"This username don't exist. Please choose another." -msgstr "Este nombre de usuario no existe, por favor ingrese otro." +#: django_authopenid/forms.py:318 +msgid "please, retype your password" +msgstr "" -#: django_authopenid/forms.py:255 -msgid "choose a username" -msgstr "elija un nombre de usuario" +#: django_authopenid/forms.py:319 +msgid "sorry, entered passwords did not match, please try again" +msgstr "" -#: django_authopenid/forms.py:257 templates/authopenid/signup.html:38 -msgid "your email address" -msgstr "su email (correo electrónico)" +#: django_authopenid/forms.py:344 +msgid "Current password" +msgstr "" -#: django_authopenid/forms.py:259 templates/authopenid/signup.html:39 -msgid "choose password" -msgstr "elija una contraseña" +#: django_authopenid/forms.py:346 +msgid "New password" +msgstr "" -#: django_authopenid/forms.py:261 templates/authopenid/signup.html:40 -msgid "retype password" -msgstr "re-ingrese la contraseña" +#: django_authopenid/forms.py:348 +msgid "Retype new password" +msgstr "" -#: django_authopenid/forms.py:335 +#: django_authopenid/forms.py:359 msgid "" "Old password is incorrect. Please enter the correct " "password." -msgstr "La antigua contraseña es incorrecta. Por favor ingrese la correcta" +msgstr "" -#: django_authopenid/forms.py:347 +#: django_authopenid/forms.py:371 msgid "new passwords do not match" -msgstr "la nueva contraseña no coincide" +msgstr "" -#: django_authopenid/forms.py:442 +#: django_authopenid/forms.py:435 +msgid "Your user name (<i>required</i>)" +msgstr "" + +#: django_authopenid/forms.py:450 msgid "Incorrect username." -msgstr "Nombre de usuario incorrecto" +msgstr "" -#: django_authopenid/urls.py:10 forum/views.py:304 forum/views.py:699 +#: django_authopenid/urls.py:9 django_authopenid/urls.py:10 +#: django_authopenid/urls.py:11 django_authopenid/urls.py:13 forum/urls.py:29 +msgid "signin/" +msgstr "" + +#: django_authopenid/urls.py:10 msgid "newquestion/" -msgstr "nuevapregunta/" +msgstr "" #: django_authopenid/urls.py:11 msgid "newanswer/" -msgstr "respuesta-nueva/" +msgstr "" #: django_authopenid/urls.py:12 msgid "signout/" -msgstr "salir/" +msgstr "" #: django_authopenid/urls.py:13 msgid "complete/" -msgstr "completado/" +msgstr "" #: django_authopenid/urls.py:15 -msgid "register/" -msgstr "registrarse/" +msgid "external-login/" +msgstr "" #: django_authopenid/urls.py:16 +msgid "register/" +msgstr "" + +#: django_authopenid/urls.py:17 msgid "signup/" -msgstr "registrarse/" +msgstr "" -#: django_authopenid/urls.py:18 +#: django_authopenid/urls.py:19 msgid "sendpw/" -msgstr "enviarcontrasena/" +msgstr "" -#: django_authopenid/urls.py:27 +#: django_authopenid/urls.py:20 django_authopenid/urls.py:24 +msgid "password/" +msgstr "" + +#: django_authopenid/urls.py:20 +msgid "confirm/" +msgstr "" + +#: django_authopenid/urls.py:23 +msgid "account_settings" +msgstr "" + +#: django_authopenid/urls.py:25 django_authopenid/urls.py:26 +#: django_authopenid/urls.py:27 django_authopenid/urls.py:28 +msgid "email/" +msgstr "" + +#: django_authopenid/urls.py:25 msgid "validate/" msgstr "" -#: django_authopenid/views.py:108 +#: django_authopenid/urls.py:26 +msgid "change/" +msgstr "" + +#: django_authopenid/urls.py:27 +msgid "sendkey/" +msgstr "" + +#: django_authopenid/urls.py:28 +msgid "verify/" +msgstr "" + +#: django_authopenid/urls.py:29 +msgid "openid/" +msgstr "" + +#: django_authopenid/urls.py:30 forum/urls.py:49 forum/urls.py:53 +msgid "delete/" +msgstr "" + +#: django_authopenid/views.py:124 #, python-format msgid "OpenID %(openid_url)s is invalid" -msgstr "El OpenID %(openid_url)s no es valido" +msgstr "" -#: django_authopenid/views.py:418 django_authopenid/views.py:545 -msgid "Welcome" -msgstr "Bienvenido" +#: django_authopenid/views.py:532 +msgid "Welcome email subject line" +msgstr "" -#: django_authopenid/views.py:508 +#: django_authopenid/views.py:627 msgid "Password changed." -msgstr "Contraseña modificada" +msgstr "" -#: django_authopenid/views.py:520 django_authopenid/views.py:525 -msgid "your email needs to be validated" -msgstr "su correo electrónico necesita ser validado" +#: django_authopenid/views.py:639 django_authopenid/views.py:645 +#, python-format +msgid "your email needs to be validated see %(details_url)s" +msgstr "" + +#: django_authopenid/views.py:666 +msgid "Email verification subject line" +msgstr "" + +#: django_authopenid/views.py:752 +msgid "your email was not changed" +msgstr "" -#: django_authopenid/views.py:682 django_authopenid/views.py:834 +#: django_authopenid/views.py:799 django_authopenid/views.py:951 #, python-format msgid "No OpenID %s found associated in our database" -msgstr "El OpenID %s no esta asociada en nuestra base de datos" +msgstr "" -#: django_authopenid/views.py:686 django_authopenid/views.py:841 +#: django_authopenid/views.py:803 django_authopenid/views.py:958 #, python-format msgid "The OpenID %s isn't associated to current user logged in" -msgstr "El OpenID %s no esta asociada al usuario actualmente autenticado" +msgstr "" -#: django_authopenid/views.py:694 +#: django_authopenid/views.py:811 msgid "Email Changed." -msgstr "Email modificado" +msgstr "" -#: django_authopenid/views.py:769 +#: django_authopenid/views.py:886 msgid "This OpenID is already associated with another account." -msgstr "Este OpenID ya está asociada a otra cuenta." +msgstr "" -#: django_authopenid/views.py:774 +#: django_authopenid/views.py:891 #, python-format msgid "OpenID %s is now associated with your account." -msgstr "El OpenID %s está ahora asociada con tu cuenta." +msgstr "" -#: django_authopenid/views.py:844 +#: django_authopenid/views.py:961 msgid "Account deleted." -msgstr "Cuenta borrada." +msgstr "" -#: django_authopenid/views.py:884 +#: django_authopenid/views.py:1004 msgid "Request for new password" -msgstr "Pedir nueva contraseña" +msgstr "" -#: django_authopenid/views.py:897 -msgid "A new password has been sent to your email address." -msgstr "Una nueva contraseña ha sido enviada a tu cuenta de Email." +#: django_authopenid/views.py:1017 +msgid "A new password and the activation link were sent to your email address." +msgstr "" -#: django_authopenid/views.py:927 +#: django_authopenid/views.py:1047 #, python-format msgid "" "Could not change password. Confirmation key '%s' is not " "registered." msgstr "" -"No se ha podido modificar la contraseña. La clave de confirmación '%s' no " -"está registrada" -#: django_authopenid/views.py:936 +#: django_authopenid/views.py:1056 msgid "" "Can not change password. User don't exist anymore in our " "database." msgstr "" -"No se puede cambiar la contraseña. El usuario no existe más en nuestra base " -"de datos." -#: django_authopenid/views.py:945 +#: django_authopenid/views.py:1065 #, python-format msgid "Password changed for %s. You may now sign in." -msgstr "Contraseña cambiada por %s. Ahora puedes ingresar." +msgstr "" + +#: forum/auth.py:484 +msgid "Your question and all of it's answers have been deleted" +msgstr "" + +#: forum/auth.py:486 +msgid "Your question has been deleted" +msgstr "" + +#: forum/auth.py:489 +msgid "The question and all of it's answers have been deleted" +msgstr "" + +#: forum/auth.py:491 +msgid "The question has been deleted" +msgstr "" #: forum/const.py:8 msgid "duplicate question" -msgstr "pregunta duplicada" +msgstr "" #: forum/const.py:9 -msgid "question if off-topic or not relevant" -msgstr "pregunta esta fuera de tema o no es relevante" +msgid "question is off-topic or not relevant" +msgstr "" #: forum/const.py:10 msgid "too subjective and argumentative" -msgstr "demasiado subjetiva o argumentativa" +msgstr "" #: forum/const.py:11 msgid "is not an answer to the question" -msgstr "no es una respuesta a la pregunta" +msgstr "" #: forum/const.py:12 msgid "the question is answered, right answer was accepted" -msgstr "la pregunta esta respondida, se ha aceptado la respuesta correcta" +msgstr "" #: forum/const.py:13 msgid "problem is not reproducible or outdated" -msgstr "el problema no es reproducible o caducó" +msgstr "" #: forum/const.py:15 msgid "question contains offensive inappropriate, or malicious remarks" -msgstr "la pregunta contiene frases ofensivas, inapropiadas o maliciosas." +msgstr "" #: forum/const.py:16 msgid "spam or advertising" -msgstr "spam o publicidad" +msgstr "" -#: forum/const.py:56 +#: forum/const.py:57 msgid "question" -msgstr "pregunta" +msgstr "" -#: forum/const.py:57 templates/book.html:110 +#: forum/const.py:58 templates/book.html:110 msgid "answer" -msgstr "respuesta" +msgstr "" -#: forum/const.py:58 +#: forum/const.py:59 msgid "commented question" -msgstr "pregunta comentada" +msgstr "" -#: forum/const.py:59 +#: forum/const.py:60 msgid "commented answer" -msgstr "respuesta comentada" +msgstr "" -#: forum/const.py:60 +#: forum/const.py:61 msgid "edited question" -msgstr "pregunta editada" +msgstr "" -#: forum/const.py:61 +#: forum/const.py:62 msgid "edited answer" -msgstr "respuesta editada" +msgstr "" -#: forum/const.py:62 +#: forum/const.py:63 msgid "received award" -msgstr "premio recibido" +msgstr "" -#: forum/const.py:63 +#: forum/const.py:64 msgid "marked best answer" -msgstr "marcada como mejor respuesta" +msgstr "" -#: forum/const.py:64 +#: forum/const.py:65 msgid "upvoted" -msgstr "votada positivo" +msgstr "" -#: forum/const.py:65 +#: forum/const.py:66 msgid "downvoted" -msgstr "votada negativo" +msgstr "" -#: forum/const.py:66 +#: forum/const.py:67 msgid "canceled vote" -msgstr "voto cancelado" +msgstr "" -#: forum/const.py:67 +#: forum/const.py:68 msgid "deleted question" -msgstr "pregunta borrada" +msgstr "" -#: forum/const.py:68 +#: forum/const.py:69 msgid "deleted answer" -msgstr "respuesta borrada" +msgstr "" -#: forum/const.py:69 +#: forum/const.py:70 msgid "marked offensive" -msgstr "marcada como ofensiva" +msgstr "" -#: forum/const.py:70 +#: forum/const.py:71 msgid "updated tags" -msgstr "etiquetas actualizadas" +msgstr "" -#: forum/const.py:71 +#: forum/const.py:72 msgid "selected favorite" -msgstr "seleccionada como favorita" +msgstr "" -#: forum/const.py:72 +#: forum/const.py:73 msgid "completed user profile" -msgstr "completó perfil de usuario" +msgstr "" -#: forum/const.py:83 +#: forum/const.py:74 +msgid "email update sent to user" +msgstr "" + +#: forum/const.py:85 msgid "[closed]" -msgstr "[cerrada]" +msgstr "" -#: forum/const.py:84 +#: forum/const.py:86 msgid "[deleted]" -msgstr "[borrada]" +msgstr "" -#: forum/const.py:85 +#: forum/const.py:87 forum/views.py:777 forum/views.py:796 msgid "initial version" -msgstr "versión inicial" +msgstr "" -#: forum/const.py:86 +#: forum/const.py:88 msgid "retagged" -msgstr "re-etiquetada" +msgstr "" + +#: forum/const.py:92 +msgid "exclude ignored tags" +msgstr "" + +#: forum/const.py:92 +msgid "allow only selected tags" +msgstr "" #: forum/feed.py:18 msgid " - " -msgstr " - " +msgstr "" #: forum/feed.py:18 msgid "latest questions" -msgstr "últimas preguntas" +msgstr "" -#: forum/forms.py:14 templates/answer_edit_tips.html:33 -#: templates/answer_edit_tips.html.py:37 templates/question_edit_tips.html:31 -#: templates/question_edit_tips.html:36 +#: forum/feed.py:19 forum/urls.py:57 +msgid "question/" +msgstr "" + +#: forum/forms.py:16 templates/answer_edit_tips.html:35 +#: templates/answer_edit_tips.html.py:39 templates/question_edit_tips.html:32 +#: templates/question_edit_tips.html:37 msgid "title" -msgstr "título" +msgstr "" -#: forum/forms.py:15 +#: forum/forms.py:17 msgid "please enter a descriptive title for your question" -msgstr "ingrese un título descriptivo para su pregunta" +msgstr "" -#: forum/forms.py:20 +#: forum/forms.py:22 msgid "title must be > 10 characters" -msgstr "el título debe tener al menos 10 caracteres" +msgstr "" -#: forum/forms.py:29 +#: forum/forms.py:31 msgid "content" -msgstr "contenido" +msgstr "" -#: forum/forms.py:35 +#: forum/forms.py:37 msgid "question content must be > 10 characters" -msgstr "el contenido de la pregunta debe ser al menos de 10 caracteres" +msgstr "" -#: forum/forms.py:45 templates/header.html:30 templates/header.html.py:64 +#: forum/forms.py:47 templates/header.html:28 templates/header.html.py:62 msgid "tags" -msgstr "etiquetas" +msgstr "" -#: forum/forms.py:47 +#: forum/forms.py:49 msgid "" "Tags are short keywords, with no spaces within. Up to five tags can be used." msgstr "" -"por favor utilice espacio para separar las etiquetas (esto habilitael auto-" -"completado)" -#: forum/forms.py:54 templates/question_retag.html:38 +#: forum/forms.py:56 templates/question_retag.html:39 msgid "tags are required" -msgstr "las etiquetas son requeridas" +msgstr "" -#: forum/forms.py:58 +#: forum/forms.py:62 msgid "please use 5 tags or less" -msgstr "por favor use 5 o menos etiquetas" +msgstr "" -#: forum/forms.py:61 +#: forum/forms.py:65 msgid "tags must be shorter than 20 characters" -msgstr "las etiquetas deben ser menores a 20 caracteres" +msgstr "" -#: forum/forms.py:65 +#: forum/forms.py:69 msgid "" "please use following characters in tags: letters 'a-z', numbers, and " "characters '.-_#'" msgstr "" -"por favor use solo los siguientes caracteres en los nombres de etiquetas: " -"letras 'a-z', números y caracteres '.-_#'" -#: forum/forms.py:75 templates/index.html:57 templates/question.html:209 -#: templates/question.html.py:395 templates/questions.html:58 -#: templates/questions.html.py:70 templates/unanswered.html:48 -#: templates/unanswered.html.py:60 +#: forum/forms.py:79 templates/index.html:62 templates/index.html.py:74 +#: templates/post_contributor_info.html:7 +#: templates/question_summary_list_roll.html:26 +#: templates/question_summary_list_roll.html:38 templates/questions.html:96 +#: templates/questions.html.py:108 templates/unanswered.html:51 +#: templates/unanswered.html.py:63 msgid "community wiki" -msgstr "wiki de comunidad" +msgstr "" -#: forum/forms.py:76 +#: forum/forms.py:80 msgid "" "if you choose community wiki option, the question and answer do not generate " "points and name of author will not be shown" msgstr "" -"si marca la opción 'wiki de comunidad', la pregunta y respuestas no generan " -"puntos y el nombre del autor no será mostrado" -#: forum/forms.py:89 +#: forum/forms.py:96 msgid "update summary:" -msgstr "resumen de modificación" +msgstr "" -#: forum/forms.py:90 +#: forum/forms.py:97 msgid "" "enter a brief summary of your revision (e.g. fixed spelling, grammar, " "improved style, this field is optional)" msgstr "" -"ingresa un breve resumen de tu revisión (ej. error ortográfico, gramática, " -"mejoras de estilo. Este campo es opcional." -#: forum/forms.py:175 +#: forum/forms.py:100 +msgid "Automatically accept user's contributions for the email updates" +msgstr "" + +#: forum/forms.py:113 +msgid "Your name:" +msgstr "" + +#: forum/forms.py:114 +msgid "Email (not shared with anyone):" +msgstr "" + +#: forum/forms.py:115 +msgid "Your message:" +msgstr "" + +#: forum/forms.py:198 msgid "this email does not have to be linked to gravatar" -msgstr "este email no tiene porque estar asociado a un Gravatar" +msgstr "" -#: forum/forms.py:176 +#: forum/forms.py:199 +msgid "Screen name" +msgstr "" + +#: forum/forms.py:200 msgid "Real name" -msgstr "Nombre real" +msgstr "" -#: forum/forms.py:177 +#: forum/forms.py:201 msgid "Website" -msgstr "Sitio Web" +msgstr "" -#: forum/forms.py:178 +#: forum/forms.py:202 msgid "Location" -msgstr "Ubicación" +msgstr "" -#: forum/forms.py:179 +#: forum/forms.py:203 msgid "Date of birth" -msgstr "Fecha de nacimiento" +msgstr "" -#: forum/forms.py:179 +#: forum/forms.py:203 msgid "will not be shown, used to calculate age, format: YYYY-MM-DD" -msgstr "no será mostrado, usado para calcular la edad. Formato: YYY-MM-DD" +msgstr "" -#: forum/forms.py:180 templates/authopenid/settings.html:21 +#: forum/forms.py:204 templates/authopenid/settings.html:21 msgid "Profile" -msgstr "Perfil" +msgstr "" -#: forum/forms.py:207 forum/forms.py:208 +#: forum/forms.py:232 forum/forms.py:233 msgid "this email has already been registered, please use another one" -msgstr "este email ya ha sido registrado, por favor use otro" +msgstr "" + +#: forum/forms.py:239 +msgid "Choose email tag filter" +msgstr "" + +#: forum/forms.py:254 forum/forms.py:255 +msgid "weekly" +msgstr "" + +#: forum/forms.py:254 forum/forms.py:255 +msgid "no email" +msgstr "" + +#: forum/forms.py:255 +msgid "daily" +msgstr "" + +#: forum/forms.py:270 +msgid "Asked by me" +msgstr "" + +#: forum/forms.py:273 +msgid "Answered by me" +msgstr "" + +#: forum/forms.py:276 +msgid "Individually selected" +msgstr "" + +#: forum/forms.py:279 +msgid "Entire forum (tag filtered)" +msgstr "" + +#: forum/models.py:52 +msgid "Entire forum" +msgstr "" + +#: forum/models.py:53 +msgid "Questions that I asked" +msgstr "" + +#: forum/models.py:54 +msgid "Questions that I answered" +msgstr "" -#: forum/models.py:246 +#: forum/models.py:55 +msgid "Individually selected questions" +msgstr "" + +#: forum/models.py:58 +msgid "Weekly" +msgstr "" + +#: forum/models.py:59 +msgid "Daily" +msgstr "" + +#: forum/models.py:60 +msgid "No email" +msgstr "" + +#: forum/models.py:321 +#, python-format msgid "%(author)s modified the question" -msgstr "%(author)s modificó la pregunta" +msgstr "" -#: forum/models.py:250 +#: forum/models.py:325 #, python-format msgid "%(people)s posted %(new_answer_count)s new answers" -msgstr "%(people)s publicaron %(new_answer_count)s nuevas respuestas" +msgstr "" -#: forum/models.py:255 +#: forum/models.py:330 #, python-format msgid "%(people)s commented the question" -msgstr "%(people)s comentarion la pregunta" +msgstr "" -#: forum/models.py:260 +#: forum/models.py:335 #, python-format msgid "%(people)s commented answers" -msgstr "%(people)s comentaron la respuesta" +msgstr "" -#: forum/models.py:262 +#: forum/models.py:337 #, python-format msgid "%(people)s commented an answer" -msgstr "%(people)s comentaron la respuesta" +msgstr "" -#: forum/models.py:306 forum/models.py:418 -msgid "revisions" -msgstr "revisiones/" +#: forum/models.py:368 +msgid "interesting" +msgstr "" + +#: forum/models.py:368 +msgid "ignored" +msgstr "" -#: forum/models.py:441 templates/badges.html:51 +#: forum/models.py:538 templates/badges.html:53 msgid "gold" -msgstr "oro" +msgstr "" -#: forum/models.py:442 templates/badges.html:59 +#: forum/models.py:539 templates/badges.html:61 msgid "silver" -msgstr "plata" +msgstr "" -#: forum/models.py:443 templates/badges.html:66 +#: forum/models.py:540 templates/badges.html:68 msgid "bronze" -msgstr "bronce" +msgstr "" + +#: forum/urls.py:26 +msgid "upfiles/" +msgstr "" + +#: forum/urls.py:30 +msgid "about/" +msgstr "" + +#: forum/urls.py:31 +msgid "faq/" +msgstr "" + +#: forum/urls.py:32 +msgid "privacy/" +msgstr "" + +#: forum/urls.py:33 +msgid "logout/" +msgstr "" + +#: forum/urls.py:34 forum/urls.py:35 forum/urls.py:36 forum/urls.py:53 +msgid "answers/" +msgstr "" + +#: forum/urls.py:34 forum/urls.py:46 forum/urls.py:49 forum/urls.py:53 +msgid "comments/" +msgstr "" + +#: forum/urls.py:35 forum/urls.py:40 forum/urls.py:75 +#: templates/user_info.html:45 +msgid "edit/" +msgstr "" + +#: forum/urls.py:36 forum/urls.py:45 +msgid "revisions/" +msgstr "" + +#: forum/urls.py:37 forum/urls.py:38 forum/urls.py:39 forum/urls.py:40 +#: forum/urls.py:41 forum/urls.py:42 forum/urls.py:43 forum/urls.py:44 +#: forum/urls.py:45 forum/urls.py:46 forum/urls.py:49 +msgid "questions/" +msgstr "" + +#: forum/urls.py:38 forum/urls.py:85 +msgid "ask/" +msgstr "" + +#: forum/urls.py:39 +msgid "unanswered/" +msgstr "" + +#: forum/urls.py:41 +msgid "close/" +msgstr "" + +#: forum/urls.py:42 +msgid "reopen/" +msgstr "" + +#: forum/urls.py:43 +msgid "answer/" +msgstr "" + +#: forum/urls.py:44 +msgid "vote/" +msgstr "" + +#: forum/urls.py:47 +msgid "command/" +msgstr "" + +#: forum/urls.py:58 forum/urls.py:59 +msgid "tags/" +msgstr "" + +#: forum/urls.py:61 forum/urls.py:65 +msgid "mark-tag/" +msgstr "" + +#: forum/urls.py:61 +msgid "interesting/" +msgstr "" + +#: forum/urls.py:65 +msgid "ignored/" +msgstr "" + +#: forum/urls.py:69 +msgid "unmark-tag/" +msgstr "" + +#: forum/urls.py:73 forum/urls.py:75 forum/urls.py:76 +msgid "users/" +msgstr "" + +#: forum/urls.py:74 +msgid "moderate-user/" +msgstr "" + +#: forum/urls.py:77 forum/urls.py:78 +msgid "badges/" +msgstr "" + +#: forum/urls.py:79 +msgid "messages/" +msgstr "" + +#: forum/urls.py:79 +msgid "markread/" +msgstr "" + +#: forum/urls.py:81 +msgid "nimda/" +msgstr "" + +#: forum/urls.py:83 +msgid "upload/" +msgstr "" + +#: forum/urls.py:84 forum/urls.py:85 forum/urls.py:86 +msgid "books/" +msgstr "" + +#: forum/urls.py:87 +msgid "search/" +msgstr "" + +#: forum/urls.py:88 +msgid "feedback/" +msgstr "" + +#: forum/urls.py:89 +msgid "account/" +msgstr "" #: forum/user.py:16 templates/user_tabs.html:7 msgid "overview" -msgstr "vista general" +msgstr "" #: forum/user.py:17 msgid "user profile" -msgstr "perfil de usuario" +msgstr "" #: forum/user.py:18 msgid "user profile overview" -msgstr "vista general del perfil de usuario" +msgstr "" #: forum/user.py:24 templates/user_tabs.html:9 msgid "recent activity" -msgstr "actividades recientes" +msgstr "" #: forum/user.py:25 msgid "recent user activity" -msgstr "actividades recientes del usuario" +msgstr "" #: forum/user.py:26 msgid "profile - recent activity" -msgstr "perfil - actividades recientes" +msgstr "" #: forum/user.py:33 templates/user_tabs.html:13 msgid "responses" -msgstr "respuestas" +msgstr "" #: forum/user.py:34 templates/user_tabs.html:12 msgid "comments and answers to others questions" -msgstr "comentarios y respuestas a preguntas de otros" +msgstr "" #: forum/user.py:35 msgid "profile - responses" -msgstr "perfil - respuestas" +msgstr "" -#: forum/user.py:42 templates/user_info.html:23 templates/users.html:26 +#: forum/user.py:42 templates/users.html:26 msgid "reputation" -msgstr "reputación" +msgstr "" #: forum/user.py:43 msgid "user reputation in the community" -msgstr "reputación del usuario en la comunidad" +msgstr "" #: forum/user.py:44 msgid "profile - user reputation" -msgstr "perfil - reputación del usuario" +msgstr "" #: forum/user.py:50 msgid "favorite questions" -msgstr "preguntas favoritas" +msgstr "" #: forum/user.py:51 msgid "users favorite questions" -msgstr "preguntas favoritas de los usuarios" +msgstr "" #: forum/user.py:52 msgid "profile - favorite questions" -msgstr "perfil - preguntas favoritas" +msgstr "" #: forum/user.py:59 templates/user_tabs.html:20 msgid "casted votes" -msgstr "votos" +msgstr "" #: forum/user.py:60 templates/user_tabs.html:20 msgid "user vote record" -msgstr "historial de votación" +msgstr "" #: forum/user.py:61 msgid "profile - votes" -msgstr "perfil - votos" +msgstr "" -#: forum/user.py:68 -msgid "preferences" -msgstr "preferencias" +#: forum/user.py:68 templates/user_tabs.html:28 +msgid "email subscriptions" +msgstr "" #: forum/user.py:69 templates/user_tabs.html:27 -msgid "user preference settings" -msgstr "preferencias del usuario" +msgid "email subscription settings" +msgstr "" #: forum/user.py:70 -msgid "profile - user preferences" -msgstr "perfil - preferencia de " +msgid "profile - email subscriptions" +msgstr "" + +#: forum/views.py:141 +msgid "Q&A forum feedback" +msgstr "" + +#: forum/views.py:142 +msgid "Thanks for the feedback!" +msgstr "" -#: forum/views.py:947 +#: forum/views.py:150 +msgid "We look forward to hearing your feedback! Please, give it next time :)" +msgstr "" + +#: forum/views.py:1080 #, python-format -msgid "subscription saved, %(email)s needs validation" -msgstr "subscripción guardada, %(email)s necesita validación" +msgid "subscription saved, %(email)s needs validation, see %(details_url)s" +msgstr "" + +#: forum/views.py:1088 +msgid "email update frequency has been set to daily" +msgstr "" + +#: forum/views.py:1965 forum/views.py:1969 +msgid "changes saved" +msgstr "" + +#: forum/views.py:1975 +msgid "email updates canceled" +msgstr "" -#: forum/views.py:1860 +#: forum/views.py:2142 msgid "uploading images is limited to users with >60 reputation points" -msgstr "para subir imagenes debes tener más de 60 puntos de reputación" +msgstr "" -#: forum/views.py:1862 +#: forum/views.py:2144 msgid "allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'" msgstr "" -"los tipos de archivos permitidos son 'jpg', 'jpeg', 'gif', 'bmp', 'png', " -"'tiff'" -#: forum/views.py:1864 +#: forum/views.py:2146 #, python-format msgid "maximum upload file size is %sK" -msgstr "tamaño máximo permitido es archivo %sK" +msgstr "" -#: forum/views.py:1866 +#: forum/views.py:2148 #, python-format msgid "" "Error uploading file. Please contact the site administrator. Thank you. %s" msgstr "" -"Error al subir el archivo. Por favor, contacte al administrador. Gracias. %s" -#: forum/management/commands/send_email_alerts.py:35 -msgid "updates from website" -msgstr "actualizaciones del sitio" +#: forum/management/commands/send_email_alerts.py:233 +msgid "email update message subject" +msgstr "" + +#: forum/management/commands/send_email_alerts.py:234 +#, python-format +msgid "%(name)s, this is an update message header for a question" +msgid_plural "%(name)s, this is an update message header for %(num)d questions" +msgstr[0] "" +msgstr[1] "" + +#: forum/management/commands/send_email_alerts.py:243 +#: forum/management/commands/send_email_alerts.py:258 +msgid "new question" +msgstr "" + +#: forum/management/commands/send_email_alerts.py:268 +#, python-format +msgid "There is also one question which was recently " +msgid_plural "" +"There are also %(num)d more questions which were recently updated " +msgstr[0] "" +msgstr[1] "" + +#: forum/management/commands/send_email_alerts.py:273 +msgid "" +"Perhaps you could look up previously sent forum reminders in your mailbox." +msgstr "" + +#: forum/management/commands/send_email_alerts.py:278 +#, python-format +msgid "" +"go to %(link)s to change frequency of email updates or %(email)s " +"administrator" +msgstr "" -#: forum/templatetags/extra_tags.py:143 forum/templatetags/extra_tags.py:172 -#: templates/header.html:35 +#: forum/templatetags/extra_tags.py:163 forum/templatetags/extra_tags.py:192 +#: templates/header.html:33 msgid "badges" -msgstr "distinciones" +msgstr "" -#: forum/templatetags/extra_tags.py:144 forum/templatetags/extra_tags.py:171 +#: forum/templatetags/extra_tags.py:164 forum/templatetags/extra_tags.py:191 msgid "reputation points" -msgstr "puntos de reputación" +msgstr "" + +#: forum/templatetags/extra_tags.py:247 +msgid "%b %d at %H:%M" +msgstr "" + +#: forum/templatetags/extra_tags.py:249 +msgid "%b %d '%y at %H:%M" +msgstr "" + +#: forum/templatetags/extra_tags.py:251 +msgid "2 days ago" +msgstr "" + +#: forum/templatetags/extra_tags.py:253 +msgid "yesterday" +msgstr "" + +#: forum/templatetags/extra_tags.py:255 +#, python-format +msgid "%(hr)d hour ago" +msgid_plural "%(hr)d hours ago" +msgstr[0] "" +msgstr[1] "" + +#: forum/templatetags/extra_tags.py:257 +#, python-format +msgid "%(min)d min ago" +msgid_plural "%(min)d mins ago" +msgstr[0] "" +msgstr[1] "" -#: forum/templatetags/extra_tags.py:225 -msgid " ago" -msgstr " atras" +#: middleware/anon_user.py:33 +#, python-format +msgid "first time greeting with %(url)s" +msgstr "" #: templates/404.html:24 msgid "Sorry, could not find the page you requested." -msgstr "Disculpe, no se pudo encontrar la página que solicito." +msgstr "" #: templates/404.html:26 msgid "This might have happened for the following reasons:" -msgstr "Esto puede haber sucedido por alguno de los siguientes motivos:" +msgstr "" #: templates/404.html:28 msgid "this question or answer has been deleted;" -msgstr "esta pregunta o respuesta ha sido borrada;" +msgstr "" #: templates/404.html:29 msgid "url has error - please check it;" -msgstr "la url tiene un error - por favor compruebelo;" +msgstr "" #: templates/404.html:30 msgid "" "the page you tried to visit is protected or you don't have sufficient " "points, see" msgstr "" -"La pagina que intentas acceder esta protegida o no tienes los puntos de " -"reputación suficientes, ver" #: templates/404.html:31 msgid "if you believe this error 404 should not have occured, please" -msgstr "si consideras que este error 404 no debería haber sucedido, por favor" +msgstr "" #: templates/404.html:32 msgid "report this problem" -msgstr "reporta este problema" +msgstr "" #: templates/404.html:41 templates/500.html:27 msgid "back to previous page" -msgstr "volver a la página siguiente" +msgstr "" #: templates/404.html:42 msgid "see all questions" -msgstr "ver todas las preguntas" +msgstr "" #: templates/404.html:43 msgid "see all tags" -msgstr "ver todas las tags" +msgstr "" #: templates/500.html:22 msgid "sorry, system error" -msgstr "lo sentimos, ha habido un error del sistema" +msgstr "" #: templates/500.html:24 msgid "system error log is recorded, error will be fixed as soon as possible" msgstr "" -"el error del sistema ha sido registrado, y será solucionado lo antes postible" #: templates/500.html:25 msgid "please report the error to the site administrators if you wish" -msgstr "por favor reportar el error al administrador de ser posible" +msgstr "" #: templates/500.html:28 msgid "see latest questions" -msgstr "ver ultimas preguntas" +msgstr "" #: templates/500.html:29 msgid "see tags" -msgstr "ver tags" +msgstr "" #: templates/about.html:6 templates/about.html.py:11 msgid "About" -msgstr "Acerca de" +msgstr "" -#: templates/answer_edit.html:4 templates/answer_edit.html.py:47 +#: templates/answer_edit.html:5 templates/answer_edit.html.py:48 msgid "Edit answer" -msgstr "Editar respuesta" +msgstr "" -#: templates/answer_edit.html:24 templates/answer_edit.html.py:27 -#: templates/ask.html:25 templates/ask.html.py:28 templates/question.html:43 -#: templates/question.html.py:46 templates/question_edit.html:27 +#: templates/answer_edit.html:25 templates/answer_edit.html.py:28 +#: templates/ask.html:26 templates/ask.html.py:29 templates/question.html:45 +#: templates/question.html.py:48 templates/question_edit.html:25 +#: templates/question_edit.html.py:28 msgid "hide preview" -msgstr "ocultar previsualización" +msgstr "" -#: templates/answer_edit.html:27 templates/ask.html:28 -#: templates/question.html:46 templates/question_edit.html:27 +#: templates/answer_edit.html:28 templates/ask.html:29 +#: templates/question.html:48 templates/question_edit.html:28 msgid "show preview" -msgstr "ver previsualización" +msgstr "" -#: templates/answer_edit.html:47 templates/question_edit.html:65 -#: templates/question_retag.html:52 templates/revisions_answer.html:36 -#: templates/revisions_question.html:36 +#: templates/answer_edit.html:48 templates/question_edit.html:66 +#: templates/question_retag.html:53 templates/revisions_answer.html:38 +#: templates/revisions_question.html:38 msgid "back" -msgstr "volver" +msgstr "" -#: templates/answer_edit.html:52 templates/question_edit.html:70 -#: templates/revisions_answer.html:47 templates/revisions_question.html:47 +#: templates/answer_edit.html:53 templates/question_edit.html:71 +#: templates/revisions_answer.html:52 templates/revisions_question.html:52 msgid "revision" -msgstr "revisión" +msgstr "" -#: templates/answer_edit.html:55 templates/question_edit.html:74 +#: templates/answer_edit.html:56 templates/question_edit.html:75 msgid "select revision" -msgstr "seleccionar revisión" +msgstr "" -#: templates/answer_edit.html:62 templates/ask.html:94 -#: templates/question.html:467 templates/question_edit.html:91 +#: templates/answer_edit.html:63 templates/ask.html:97 +#: templates/question.html:442 templates/question_edit.html:92 msgid "Toggle the real time Markdown editor preview" -msgstr "Activar la visualización en tiempo real de Markdown" +msgstr "" -#: templates/answer_edit.html:62 templates/ask.html:94 -#: templates/question.html:467 templates/question_edit.html:91 +#: templates/answer_edit.html:63 templates/ask.html:97 +#: templates/question.html:443 templates/question_edit.html:92 msgid "toggle preview" -msgstr "Activar previsualización" +msgstr "" -#: templates/answer_edit.html:71 templates/question_edit.html:115 -#: templates/question_retag.html:73 +#: templates/answer_edit.html:72 templates/question_edit.html:124 +#: templates/question_retag.html:74 msgid "Save edit" -msgstr "Guardar la edición" +msgstr "" -#: templates/answer_edit.html:72 templates/close.html:29 -#: templates/question_edit.html:116 templates/question_retag.html:74 -#: templates/reopen.html:30 templates/user_edit.html:83 -#: templates/authopenid/changeemail.html:34 +#: templates/answer_edit.html:73 templates/close.html:29 +#: templates/feedback.html:50 templates/question_edit.html:125 +#: templates/question_retag.html:75 templates/reopen.html:30 +#: templates/user_edit.html:87 templates/authopenid/changeemail.html:40 msgid "Cancel" -msgstr "Cancelar" +msgstr "" #: templates/answer_edit_tips.html:4 msgid "answer tips" -msgstr "sugerencias sobre respuestas" +msgstr "" #: templates/answer_edit_tips.html:7 msgid "please make your answer relevant to this community" -msgstr "por favor, haz que tu respuesta sea relevante a esta comunidad" +msgstr "" #: templates/answer_edit_tips.html:10 msgid "try to give an answer, rather than engage into a discussion" -msgstr "intenta dar una respuesta, más que entablar un debate o discusión" +msgstr "" #: templates/answer_edit_tips.html:13 msgid "please try to provide details" -msgstr "por favor, intenta brindar detalles" +msgstr "" #: templates/answer_edit_tips.html:16 templates/question_edit_tips.html:13 msgid "be clear and concise" -msgstr "ser claro y conciso" +msgstr "" -#: templates/answer_edit_tips.html:19 templates/question_edit_tips.html:16 +#: templates/answer_edit_tips.html:20 templates/question_edit_tips.html:17 msgid "see frequently asked questions" -msgstr "ver preguntas frecuentes" +msgstr "" -#: templates/answer_edit_tips.html:24 templates/question_edit_tips.html:22 +#: templates/answer_edit_tips.html:26 templates/question_edit_tips.html:23 msgid "Markdown tips" -msgstr "sugerencias de Markdown" +msgstr "" -#: templates/answer_edit_tips.html:27 templates/question_edit_tips.html:25 +#: templates/answer_edit_tips.html:29 templates/question_edit_tips.html:26 msgid "*italic* or __italic__" -msgstr "*itálica* o __itálica__" +msgstr "" -#: templates/answer_edit_tips.html:30 templates/question_edit_tips.html:28 +#: templates/answer_edit_tips.html:32 templates/question_edit_tips.html:29 msgid "**bold** or __bold__" -msgstr "**negrita** o __negrita__" +msgstr "" -#: templates/answer_edit_tips.html:33 templates/question_edit_tips.html:31 +#: templates/answer_edit_tips.html:35 templates/question_edit_tips.html:32 msgid "link" -msgstr "enlace" +msgstr "" -#: templates/answer_edit_tips.html:33 templates/answer_edit_tips.html.py:37 -#: templates/question_edit_tips.html:31 templates/question_edit_tips.html:36 +#: templates/answer_edit_tips.html:35 templates/answer_edit_tips.html.py:39 +#: templates/question_edit_tips.html:32 templates/question_edit_tips.html:37 msgid "text" -msgstr "texto" +msgstr "" -#: templates/answer_edit_tips.html:37 templates/question_edit_tips.html:36 +#: templates/answer_edit_tips.html:39 templates/question_edit_tips.html:37 msgid "image" -msgstr "imagen" +msgstr "" -#: templates/answer_edit_tips.html:41 templates/question_edit_tips.html:40 +#: templates/answer_edit_tips.html:43 templates/question_edit_tips.html:41 msgid "numbered list:" -msgstr "lista numerada" +msgstr "" -#: templates/answer_edit_tips.html:46 templates/question_edit_tips.html:45 +#: templates/answer_edit_tips.html:48 templates/question_edit_tips.html:46 msgid "basic HTML tags are also supported" -msgstr "etiquetas básicas de HTML permitidas" +msgstr "" -#: templates/answer_edit_tips.html:49 templates/question_edit_tips.html:48 +#: templates/answer_edit_tips.html:52 templates/question_edit_tips.html:50 msgid "learn more about Markdown" -msgstr "aprender mas sobre Markdown" +msgstr "" -#: templates/ask.html:4 templates/ask.html.py:60 +#: templates/ask.html:5 templates/ask.html.py:61 msgid "Ask a question" -msgstr "Hacer una pregunta" +msgstr "" -#: templates/ask.html:67 +#: templates/ask.html:68 msgid "login to post question info" msgstr "" -"<span class='strong big'>Puedes comenzar a realizar tu pregunta de forma " -"anonima</span> - actualmente no te encuentras Ingresado. Cuando envíes tu " -"pregunta, serás redireccionado a la página de Ingreso/registro. Tu pregunta " -"será guardada temporalemente y será enviada cuando ingreses. El proceso de " -"Ingreso/Registro es muy simple. Ingresar lleva unos 30 segundos, registrarse " -"por primera vez lleva un minuto." -#: templates/ask.html:73 +#: templates/ask.html:74 #, python-format -msgid "must have valid %(email)s to post" +msgid "" +"must have valid %(email)s to post, \n" +" see %(email_validation_faq_url)s\n" +" " msgstr "" -"<span class='strong big'>Parece ser que tu dirección de email, %(email)s no " -"ha sido validada aún.</span> Para enviar mensajes debes verificar tu " -"dirección de email, puedes ver <a href='/faq#validate'>más detalles aquí</" -"a>. <br/> Puedes enviar tu pregunta ahora y validar tu email luego. Tu " -"pregunta será guardada mientras tanto y publicada cuando valides tu email." -#: templates/ask.html:107 +#: templates/ask.html:112 templates/ask.html.py:119 +#: templates/question_edit.html:120 msgid "(required)" -msgstr "(requerido)" +msgstr "" -#: templates/ask.html:114 +#: templates/ask.html:126 msgid "Login/signup to post your question" -msgstr "Iniciar sesión/registrarse para publicar su pregunta" +msgstr "" -#: templates/ask.html:116 +#: templates/ask.html:128 msgid "Ask your question" -msgstr "Haz tu pregunta" +msgstr "" #: templates/badge.html:6 templates/badge.html.py:17 msgid "Badge" -msgstr "Distinción" +msgstr "" #: templates/badge.html:26 msgid "The users have been awarded with badges:" -msgstr "Usuarios han sido galardonados con distinciones:" +msgstr "" #: templates/badges.html:6 msgid "Badges summary" -msgstr "Resumen de distinciones" +msgstr "" -#: templates/badges.html:17 templates/user_stats.html:115 +#: templates/badges.html:17 msgid "Badges" -msgstr "Distinciones" +msgstr "" #: templates/badges.html:21 msgid "Community gives you awards for your questions, answers and votes." -msgstr "La comunidad te da distinciones por tus preguntas, respuestas y votos." +msgstr "" #: templates/badges.html:22 +#, python-format msgid "" -"Below is the list of available badges and number of times each type of badge " -"has been awarded." +"Below is the list of available badges and number \n" +" of times each type of badge has been awarded. Give us feedback at %" +"(feedback_faq_url)s.\n" +" " msgstr "" -"Debajo esta la lista de las distinciones disponibles y la cantidad de veces " -"que han sido asignadas." -#: templates/badges.html:48 +#: templates/badges.html:50 msgid "Community badges" -msgstr "Distinciones de la comunidad" +msgstr "" -#: templates/badges.html:54 +#: templates/badges.html:56 msgid "gold badge description" msgstr "" -"Las distinciones de Oro son excepcionales. Para obtenerla debes demostrar un " -"profundo conocimiento y habilidad además de participar activamente en la " -"comunidad. La distinción de Oro es la condecoración máxima en esta comunidad" -#: templates/badges.html:62 +#: templates/badges.html:64 msgid "silver badge description" msgstr "" -"Obtener una distinción de Plata requiere de paciencia. Si has logrado una, " -"quiere decir que haz significativamente aportado a esta comunidad." -#: templates/badges.html:65 +#: templates/badges.html:67 msgid "bronze badge: often given as a special honor" msgstr "" -"distinción de bronce: con frecuencia entregada como reconocimiento especial." -#: templates/badges.html:69 +#: templates/badges.html:71 msgid "bronze badge description" msgstr "" -"Si eres un usuario activo de esta comunidad, recibirás esta distinción - de " -"todas maneras es un honor especial." #: templates/book.html:7 msgid "reading channel" -msgstr "canal de lectura" +msgstr "" #: templates/book.html:26 msgid "[author]" -msgstr "[autor]" +msgstr "" #: templates/book.html:30 msgid "[publisher]" -msgstr "[editorial]" +msgstr "" #: templates/book.html:34 msgid "[publication date]" -msgstr "[fecha de publicación]" +msgstr "" #: templates/book.html:38 msgid "[price]" -msgstr "[precio]" +msgstr "" #: templates/book.html:39 msgid "currency unit" -msgstr "unidad de moneda" +msgstr "" #: templates/book.html:42 msgid "[pages]" -msgstr "[páginas]" +msgstr "" #: templates/book.html:43 msgid "pages abbreviation" -msgstr "abreviación de páginas" +msgstr "" #: templates/book.html:46 msgid "[tags]" -msgstr "[etiquetas]" +msgstr "" #: templates/book.html:56 msgid "author blog" -msgstr "blog del autor" +msgstr "" #: templates/book.html:62 msgid "book directory" -msgstr "directorio del libro" +msgstr "" #: templates/book.html:66 msgid "buy online" -msgstr "comprar en-linea" +msgstr "" #: templates/book.html:79 msgid "reader questions" -msgstr "pregunta de lector" +msgstr "" #: templates/book.html:82 msgid "ask the author" -msgstr "preguntar al autor" +msgstr "" #: templates/book.html:88 templates/book.html.py:93 -#: templates/users_questions.html:17 +#: templates/users_questions.html:18 msgid "this question was selected as favorite" -msgstr "esta pregunta ha sido seleccionada como favorita" +msgstr "" #: templates/book.html:88 templates/book.html.py:93 -#: templates/users_questions.html:11 templates/users_questions.html.py:17 +#: templates/users_questions.html:11 templates/users_questions.html.py:18 msgid "number of times" -msgstr "numero de veces" +msgstr "" -#: templates/book.html:105 templates/index.html:48 templates/questions.html:46 -#: templates/unanswered.html:37 templates/users_questions.html:30 +#: templates/book.html:105 templates/index.html:50 +#: templates/question_summary_list_roll.html:14 templates/questions.html:84 +#: templates/unanswered.html:39 templates/users_questions.html:32 msgid "votes" -msgstr "votos" +msgstr "" #: templates/book.html:108 msgid "the answer has been accepted to be correct" -msgstr "la respuesta ha sido aceptada como correcta" +msgstr "" -#: templates/book.html:115 templates/index.html:49 templates/questions.html:47 -#: templates/unanswered.html:38 templates/users_questions.html:40 +#: templates/book.html:115 templates/index.html:51 +#: templates/question_summary_list_roll.html:15 templates/questions.html:85 +#: templates/unanswered.html:40 templates/users_questions.html:40 msgid "views" -msgstr "vistas" +msgstr "" -#: templates/book.html:125 templates/index.html:69 templates/question.html:499 -#: templates/questions.html:84 templates/questions.html.py:156 -#: templates/tags.html:49 templates/unanswered.html:75 -#: templates/unanswered.html.py:106 templates/users_questions.html:52 +#: templates/book.html:125 templates/index.html:106 +#: templates/question.html:488 templates/question_summary_list_roll.html:52 +#: templates/questions.html:140 templates/questions.html.py:257 +#: templates/tags.html:49 templates/unanswered.html:95 +#: templates/unanswered.html.py:122 templates/users_questions.html:52 msgid "using tags" -msgstr "usando etiquetas" +msgstr "" #: templates/book.html:147 msgid "subscribe to book RSS feed" -msgstr "suscribirse al RSS del libro" +msgstr "" -#: templates/book.html:147 templates/index.html:118 +#: templates/book.html:147 templates/index.html:157 msgid "subscribe to the questions feed" -msgstr "suscribirse al agregado de noticias" +msgstr "" #: templates/close.html:6 templates/close.html.py:16 msgid "Close question" -msgstr "Cerrar pregunta" +msgstr "" #: templates/close.html:19 msgid "Close the question" -msgstr "Cerrar la pregunta" +msgstr "" #: templates/close.html:25 msgid "Reasons" -msgstr "Razón" +msgstr "" #: templates/close.html:28 msgid "OK to close" -msgstr "OK para cerrar" +msgstr "" #: templates/faq.html:11 msgid "Frequently Asked Questions " -msgstr "Preguntas Frecuentes" +msgstr "" #: templates/faq.html:16 msgid "What kinds of questions can I ask here?" -msgstr "¿Qué clase de preguntas puedo hacer aquí?" +msgstr "" #: templates/faq.html:17 msgid "" "Most importanly - questions should be <strong>relevant</strong> to this " "community." msgstr "" -"Por encima de todo - las preguntas deben ser <strong>relevantes</strong>a " -"esta comunidad." #: templates/faq.html:18 msgid "" "Before asking the question - please make sure to use search to see whether " "your question has alredy been answered." msgstr "" -"Antes de hacer tu pregunta - por favor usa el buscador para asegurarte que " -"la pregunta no este ya hecha." #: templates/faq.html:21 msgid "What questions should I avoid asking?" -msgstr "¿Qué preguntas debería evitar preguntar?" +msgstr "" #: templates/faq.html:22 msgid "" "Please avoid asking questions that are not relevant to this community, too " "subjective and argumentative." msgstr "" -"Evita hacer preguntas que no son relevantes a la comunidad, demasiado " -"subjetivas o argumentativas." #: templates/faq.html:27 msgid "What should I avoid in my answers?" -msgstr "¿Que debo evitar en mis respuestas?" +msgstr "" #: templates/faq.html:28 msgid "" @@ -1181,42 +1455,32 @@ msgid "" "discussions in your answers, comment facility allows some space for brief " "discussions." msgstr "" -"es un sitio de Preguntas y Respuestas, no un grupo de discusión. Por ende, " -"intenta evitar discusiones en tus respuestas. Los comentarios permiten " -"realizar pequeñas discusiones." #: templates/faq.html:32 msgid "Who moderates this community?" -msgstr "¿Quién modera esta comunidad?" +msgstr "" #: templates/faq.html:33 msgid "The short answer is: <strong>you</strong>." -msgstr "La respuesta corta es: <strong>tú</strong>" +msgstr "" #: templates/faq.html:34 msgid "This website is moderated by the users." -msgstr "Este sitio es moderado por los usuarios." +msgstr "" #: templates/faq.html:35 msgid "" "The reputation system allows users earn the authorization to perform a " "variety of moderation tasks." msgstr "" -"El sistema de reputación permite a los usuarios adquirir autorización para " -"realizar diversas tareas de moderación." #: templates/faq.html:40 msgid "How does reputation system work?" -msgstr "¿Cómo funciona el sistema de reputación?" +msgstr "" #: templates/faq.html:41 msgid "Rep system summary" msgstr "" -"Cuando una pregunta o respuesta es votada positivamente, el usuario que la " -"realizo ganará algunos puntos, que llamamos \"puntos de reputación\". Estos " -"puntos sirven a groso modo para medir la confianza que la comunidad le " -"tiene. Diversas tareas de moderación son gradualmente asignadas a los " -"usuarios basado en estos puntos de reputación." #: templates/faq.html:42 msgid "" @@ -1228,657 +1492,804 @@ msgid "" "or answer. The table below explains reputation point requirements for each " "type of moderation task." msgstr "" -"Por ejemplo, si haces una pregunta interesante o das una respuesta útil, tu " -"adición será votada positivamente. Por otro lado, si la respuesta es fuera " -"de lugar - será votada negativamente. Cada voto a favor genera <strong>10</" -"strong> puntos, cada voto en contra restará <strong>2</strong> puntos. Hay " -"un limite de <strong>200</strong> puntos que puedes acumular por pregunta o " -"respuesta. La tabla debajo explica los puntos de reputación requeridos para " -"cada tarea de moderación." -#: templates/faq.html:53 templates/user_votes.html:14 +#: templates/faq.html:53 templates/user_votes.html:15 msgid "upvote" -msgstr "votar positivo" +msgstr "" #: templates/faq.html:57 msgid "use tags" -msgstr "etiquetas usadas" +msgstr "" #: templates/faq.html:62 msgid "add comments" -msgstr "agregar comentarios" +msgstr "" -#: templates/faq.html:66 templates/user_votes.html:16 +#: templates/faq.html:66 templates/user_votes.html:17 msgid "downvote" -msgstr "votar negativo" +msgstr "" #: templates/faq.html:69 msgid "open and close own questions" -msgstr "abrir y cerrar sus propias preguntas" +msgstr "" #: templates/faq.html:73 msgid "retag questions" -msgstr "re-etiquetar preguntas" +msgstr "" -#: templates/faq.html:77 +#: templates/faq.html:78 msgid "edit community wiki questions" -msgstr "editar preguntas de la wiki comunitaria" +msgstr "" -#: templates/faq.html:81 +#: templates/faq.html:83 msgid "edit any answer" -msgstr "editar cualquier pregunta" +msgstr "" -#: templates/faq.html:85 +#: templates/faq.html:87 msgid "open any closed question" -msgstr "abrir cualquier pregunta cerrada" +msgstr "" -#: templates/faq.html:89 +#: templates/faq.html:91 msgid "delete any comment" -msgstr "borrar cualquier comentario" +msgstr "" -#: templates/faq.html:93 +#: templates/faq.html:95 msgid "delete any questions and answers and perform other moderation tasks" msgstr "" -"borrar cualquier pregunta o respuesta y realizar otras tareas de " -"administración." -#: templates/faq.html:100 +#: templates/faq.html:102 msgid "how to validate email title" -msgstr "¿Cómo validar mi correo electrónico?" +msgstr "" -#: templates/faq.html:102 -msgid "how to validate email info" -msgstr "" -"<form style='margin:0;padding:0;' action='/email/sendkey/'><p><span class=" -"\"bigger strong\">¿Cómo?</span> Si acabas de asignar o cambiar tu correo " -"electrónico - <strong>verifica tu casilla de mensajes y clickea en el link " -"incluido</strong>. <br/> El link contiene una clave generada especificamente " -"para ti. <button style='display:inline' type='submit'><strong>get a new key</" -"strong></button> y vuelve a revisar tu casilla de mensajes.</p></form><span " -"class=\"bigger strong\">¿Porqué?</span> La validación del email es requerida " -"para estar seguros the que <strong>solo tu puedes enviar mensajes</strong> " -"bajo tu concentimiento y para <strong>minimizar el spam</strong>.<br/> Con " -"tu email podrás <strong>suscribirte a actualizaciones</strong> en las " -"preguntas mas interesantes. También, cuando te registras por primera vez - " -"se crea un imagen personal única de <a href='/" -"faq#gravatar'><strong>gravatar</strong></a>." - -#: templates/faq.html:106 +#: templates/faq.html:104 +#, python-format +msgid "" +"how to validate email info with %(send_email_key_url)s %(gravatar_faq_url)s" +msgstr "" + +#: templates/faq.html:108 msgid "what is gravatar" -msgstr "¿Qué es gravatar?" +msgstr "" -#: templates/faq.html:107 +#: templates/faq.html:109 msgid "gravatar faq info" msgstr "" -"<strong>Gravatar</strong> significa <strong>g</strong>lobalmente <strong>r</" -"strong>econocido <strong>avatar</strong> - tu imagen única asociada a tu " -"email. Es simplemente una imagen que se muestra junto con tus mensajes en " -"sitios que soportan gravatar. Por defecto gravatar aparece como un cuadrado " -"rellenado con figuras parecidas a copos de nieve. Puedes <strong>seleccionar " -"tu imagen</strong> en <a href='http://gravatar.com'><strong>gravatar.com</" -"strong></a>" -#: templates/faq.html:110 +#: templates/faq.html:112 msgid "To register, do I need to create new password?" -msgstr "¿Para registrarme, debo crearme una cuenta?" +msgstr "" -#: templates/faq.html:111 +#: templates/faq.html:113 msgid "" "No, you don't have to. You can login through any service that supports " "OpenID, e.g. Google, Yahoo, AOL, etc." msgstr "" -"No tienes porqué. Puedes ingresar usando cualquiera de los servicios que " -"soportan OpenID, ej. Google, Yahoo, AOL, MyOpenID, etc." -#: templates/faq.html:112 +#: templates/faq.html:114 msgid "Login now!" -msgstr "Ingresa ahora!" +msgstr "" -#: templates/faq.html:117 +#: templates/faq.html:119 msgid "Why other people can edit my questions/answers?" -msgstr "¿Porqué otras personas pueden editar mis preguntas y respuestas?" +msgstr "" -#: templates/faq.html:118 +#: templates/faq.html:120 msgid "Goal of this site is..." msgstr "" -"El objetivo de este sitio es generar contenido valioso mediante preguntas y " -"respuestas, de forma colaborativa. " -#: templates/faq.html:118 +#: templates/faq.html:120 msgid "" "So questions and answers can be edited like wiki pages by experienced users " "of this site and this improves the overall quality of the knowledge base " "content." msgstr "" -"Entonces, las preguntas y respuestas pueden ser editadas como wiki por " -"usuarios con experiencia, y esto mejora la calidad general del conocimiento " -"acumulado." -#: templates/faq.html:119 +#: templates/faq.html:121 msgid "If this approach is not for you, we respect your choice." msgstr "" -"Si esta forma de funcionamiento no es de tu agrado, respetamos tu elección." -#: templates/faq.html:123 +#: templates/faq.html:125 msgid "Still have questions?" -msgstr "¿Aún tienes preguntas?" +msgstr "" -#: templates/faq.html:124 -msgid "Please ask your question, help make our community better!" -msgstr "Por favor haz tu pregunta, ¡ayudanos a mejorar nuestra comunidad!" +#: templates/faq.html:126 +#, python-format +msgid "" +"Please ask your question at %(ask_question_url)s, help make our community " +"better!" +msgstr "" -#: templates/faq.html:126 templates/header.html:29 templates/header.html.py:63 +#: templates/faq.html:128 templates/header.html:27 templates/header.html.py:61 msgid "questions" -msgstr "preguntas" +msgstr "" -#: templates/faq.html:126 templates/index.html:123 +#: templates/faq.html:128 templates/index.html:162 msgid "." -msgstr "." +msgstr "" + +#: templates/feedback.html:6 +msgid "Feedback" +msgstr "" + +#: templates/feedback.html:11 +msgid "Give us your feedback!" +msgstr "" + +#: templates/feedback.html:17 +#, python-format +msgid "" +"\n" +" <span class='big strong'>Dear %(user_name)s</span>, we look " +"forward to hearing your feedback. \n" +" Please type and send us your message below.\n" +" " +msgstr "" + +#: templates/feedback.html:24 +msgid "" +"\n" +" <span class='big strong'>Dear visitor</span>, we look forward to " +"hearing your feedback.\n" +" Please type and send us your message below.\n" +" " +msgstr "" -#: templates/footer.html:7 templates/header.html:14 templates/index.html:83 +#: templates/feedback.html:41 +msgid "(this field is required)" +msgstr "" + +#: templates/feedback.html:49 +msgid "Send Feedback" +msgstr "" + +#: templates/footer.html:8 templates/header.html:13 templates/index.html:120 msgid "about" -msgstr "acerca de nosotros" +msgstr "" -#: templates/footer.html:8 templates/header.html:15 templates/index.html:84 -#: templates/question_edit_tips.html:16 +#: templates/footer.html:9 templates/header.html:14 templates/index.html:121 +#: templates/question_edit_tips.html:17 msgid "faq" -msgstr "preguntas frecuentes" +msgstr "" -#: templates/footer.html:9 +#: templates/footer.html:10 msgid "blog" -msgstr "blog" +msgstr "" -#: templates/footer.html:10 +#: templates/footer.html:11 msgid "contact us" -msgstr "contactenos" +msgstr "" -#: templates/footer.html:11 +#: templates/footer.html:12 msgid "privacy policy" -msgstr "código de privacidad" +msgstr "" -#: templates/footer.html:12 +#: templates/footer.html:21 msgid "give feedback" -msgstr "envía comentarios" - -#: templates/footer.html:18 -msgid "current revision" -msgstr "revisión actual" +msgstr "" -#: templates/header.html:10 +#: templates/header.html:9 msgid "logout" -msgstr "salir" +msgstr "" -#: templates/header.html:12 templates/authopenid/signup.html:41 +#: templates/header.html:11 msgid "login" -msgstr "entrar" +msgstr "" -#: templates/header.html:23 +#: templates/header.html:21 msgid "back to home page" -msgstr "volver página principal" +msgstr "" -#: templates/header.html:31 templates/header.html.py:65 +#: templates/header.html:29 templates/header.html.py:63 msgid "users" -msgstr "usuarios" +msgstr "" -#: templates/header.html:33 +#: templates/header.html:31 msgid "books" -msgstr "libros" +msgstr "" -#: templates/header.html:36 +#: templates/header.html:34 msgid "unanswered questions" -msgstr "sin respuesta" +msgstr "" -#: templates/header.html:40 +#: templates/header.html:38 msgid "my profile" -msgstr "mi perfil" +msgstr "" -#: templates/header.html:44 +#: templates/header.html:42 msgid "ask a question" -msgstr "hacer una pregunta" +msgstr "" -#: templates/header.html:59 +#: templates/header.html:57 msgid "search" -msgstr "buscar" +msgstr "" -#: templates/index.html:7 +#: templates/index.html:8 msgid "Home" -msgstr "Inicio" +msgstr "" -#: templates/index.html:22 templates/questions.html:7 +#: templates/index.html:25 templates/questions.html:8 msgid "Questions" -msgstr "Preguntas" +msgstr "" -#: templates/index.html:24 +#: templates/index.html:27 msgid "last updated questions" -msgstr "ultimas preguntas actualizadas" +msgstr "" -#: templates/index.html:24 templates/questions.html:25 -#: templates/unanswered.html:20 +#: templates/index.html:27 templates/questions.html:51 +#: templates/unanswered.html:21 msgid "newest" -msgstr "más nuevas" +msgstr "" + +#: templates/index.html:28 templates/questions.html:52 +msgid "most recently updated questions" +msgstr "" -#: templates/index.html:25 templates/questions.html:27 +#: templates/index.html:28 templates/questions.html:52 +msgid "active" +msgstr "" + +#: templates/index.html:29 templates/questions.html:53 msgid "hottest questions" -msgstr "preguntas calientes" +msgstr "" -#: templates/index.html:25 templates/questions.html:27 +#: templates/index.html:29 templates/questions.html:53 msgid "hottest" -msgstr "más calientes" +msgstr "" -#: templates/index.html:26 templates/questions.html:28 +#: templates/index.html:30 templates/questions.html:54 msgid "most voted questions" -msgstr "preguntas más votadas" +msgstr "" -#: templates/index.html:26 templates/questions.html:28 +#: templates/index.html:30 templates/questions.html:54 msgid "most voted" -msgstr "más votadas" +msgstr "" -#: templates/index.html:27 +#: templates/index.html:31 msgid "all questions" -msgstr "todas las preguntas" +msgstr "" -#: templates/index.html:47 templates/questions.html:45 -#: templates/unanswered.html:36 templates/users_questions.html:35 +#: templates/index.html:49 templates/question_summary_list_roll.html:13 +#: templates/questions.html:83 templates/unanswered.html:38 +#: templates/users_questions.html:36 msgid "answers" -msgstr "respuestas" +msgstr "" + +#: templates/index.html:81 templates/index.html.py:95 +#: templates/questions.html:115 templates/questions.html.py:129 +#: templates/unanswered.html:70 templates/unanswered.html.py:84 +msgid "Posted:" +msgstr "" + +#: templates/index.html:84 templates/index.html.py:89 +#: templates/questions.html:118 templates/questions.html.py:123 +#: templates/unanswered.html:73 templates/unanswered.html.py:78 +msgid "Updated:" +msgstr "" -#: templates/index.html:69 templates/question.html:499 -#: templates/questions.html:84 templates/questions.html.py:156 -#: templates/tags.html:49 templates/unanswered.html:75 -#: templates/unanswered.html.py:106 templates/users_questions.html:52 +#: templates/index.html:106 templates/question.html:488 +#: templates/question_summary_list_roll.html:52 templates/questions.html:140 +#: templates/questions.html.py:257 templates/tags.html:49 +#: templates/unanswered.html:95 templates/unanswered.html.py:122 +#: templates/users_questions.html:52 msgid "see questions tagged" -msgstr "ver preguntas etiquetadas" +msgstr "" -#: templates/index.html:80 +#: templates/index.html:117 msgid "welcome to website" -msgstr "bienvenido a sitio" +msgstr "" -#: templates/index.html:89 +#: templates/index.html:128 msgid "Recent tags" -msgstr "Etiquetas recientes" +msgstr "" -#: templates/index.html:94 templates/question.html:125 +#: templates/index.html:133 templates/question.html:135 #, python-format msgid "see questions tagged '%(tagname)s'" -msgstr "ver preguntas etiquetadas '%(tagname)s'" +msgstr "" -#: templates/index.html:97 templates/index.html.py:123 +#: templates/index.html:136 templates/index.html.py:162 msgid "popular tags" -msgstr "etiquetas populares" +msgstr "" -#: templates/index.html:102 +#: templates/index.html:141 msgid "Recent awards" -msgstr "Reconocimientos recientes" +msgstr "" -#: templates/index.html:108 +#: templates/index.html:147 msgid "given to" -msgstr "dados a" +msgstr "" -#: templates/index.html:113 +#: templates/index.html:152 msgid "all awards" -msgstr "todos los reconocimientos" +msgstr "" -#: templates/index.html:118 +#: templates/index.html:157 msgid "subscribe to last 30 questions by RSS" -msgstr "suscribirse a las últimas 30 preguntas por RSS" +msgstr "" -#: templates/index.html:123 +#: templates/index.html:162 msgid "Still looking for more? See" -msgstr "¿Aún sigues buscando más? Ver" +msgstr "" -#: templates/index.html:123 +#: templates/index.html:162 msgid "complete list of questions" -msgstr "lista completa de preguntas" +msgstr "" -#: templates/index.html:123 +#: templates/index.html:162 templates/authopenid/signup.html:18 msgid "or" -msgstr "ó" +msgstr "" -#: templates/index.html:123 +#: templates/index.html:162 msgid "Please help us answer" -msgstr "Ayudanos a responder" +msgstr "" -#: templates/index.html:123 +#: templates/index.html:162 msgid "list of unanswered questions" -msgstr "lista de preguntas sin respuesta" +msgstr "" -#: templates/logout.html:6 templates/logout.html.py:17 +#: templates/logout.html:6 templates/logout.html.py:16 msgid "Logout" -msgstr "Salir" +msgstr "" -#: templates/logout.html:20 +#: templates/logout.html:19 msgid "" "As a registered user you can login with your OpenID, log out of the site or " "permanently remove your account." msgstr "" -"Como usuario registrado puedes ingresar con tu OpenID, salir del sitio o " -"eliminar de forma permanente tu cuenta." -#: templates/logout.html:21 +#: templates/logout.html:20 msgid "Logout now" -msgstr "Salir ahora" +msgstr "" #: templates/pagesize.html:6 msgid "posts per page" -msgstr "entradas por página" +msgstr "" #: templates/paginator.html:6 templates/paginator.html.py:7 msgid "previous" -msgstr "previo" +msgstr "" #: templates/paginator.html:19 msgid "current page" -msgstr "página actúal" +msgstr "" #: templates/paginator.html:22 templates/paginator.html.py:29 msgid "page number " -msgstr "número de página" +msgstr "" #: templates/paginator.html:22 templates/paginator.html.py:29 msgid "number - make blank in english" -msgstr " " +msgstr "" #: templates/paginator.html:33 msgid "next page" -msgstr "próxima página" +msgstr "" + +#: templates/post_contributor_info.html:9 +#, python-format +msgid "" +"\n" +" one revision\n" +" " +msgid_plural "" +"\n" +" %(rev_count)s revisions\n" +" " +msgstr[0] "" +msgstr[1] "" + +#: templates/post_contributor_info.html:19 +msgid "asked" +msgstr "" + +#: templates/post_contributor_info.html:22 +msgid "answered" +msgstr "" + +#: templates/post_contributor_info.html:24 +msgid "posted" +msgstr "" + +#: templates/post_contributor_info.html:45 +msgid "updated" +msgstr "" #: templates/privacy.html:6 templates/privacy.html.py:11 msgid "Privacy policy" -msgstr "Privacidad" +msgstr "" #: templates/privacy.html:15 msgid "general message about privacy" -msgstr "mensaje de privacidad" +msgstr "" #: templates/privacy.html:18 msgid "Site Visitors" -msgstr "Visitantes del Sitio" +msgstr "" #: templates/privacy.html:20 msgid "what technical information is collected about visitors" -msgstr "que información es recolectada sobre los usuarios" +msgstr "" #: templates/privacy.html:23 msgid "Personal Information" -msgstr "Información Personal" +msgstr "" #: templates/privacy.html:25 msgid "details on personal information policies" -msgstr "describir código de manejo de la información personal" +msgstr "" #: templates/privacy.html:28 msgid "Other Services" -msgstr "Otros servicios" +msgstr "" #: templates/privacy.html:30 msgid "details on sharing data with third parties" -msgstr "detalles sobre compartir información con terceros" +msgstr "" #: templates/privacy.html:35 msgid "cookie policy details" -msgstr "uso de cookies" +msgstr "" #: templates/privacy.html:37 msgid "Policy Changes" -msgstr "Cambios de Códigos" +msgstr "" #: templates/privacy.html:38 msgid "how privacy policies can be changed" -msgstr "como pueden ser cambiados los códigos de privacidad" +msgstr "" -#: templates/question.html:72 templates/question.html.py:73 -#: templates/question.html:85 templates/question.html.py:87 +#: templates/question.html:77 templates/question.html.py:78 +#: templates/question.html:94 templates/question.html.py:96 msgid "i like this post (click again to cancel)" -msgstr "Me gusta esta entrada (clickear devuelta para cancelar)" +msgstr "" -#: templates/question.html:75 templates/question.html.py:89 -#: templates/question.html:289 +#: templates/question.html:80 templates/question.html.py:98 +#: templates/question.html:257 msgid "current number of votes" -msgstr "número actual de votos" +msgstr "" -#: templates/question.html:80 templates/question.html.py:81 -#: templates/question.html:94 templates/question.html.py:95 +#: templates/question.html:89 templates/question.html.py:90 +#: templates/question.html:103 templates/question.html.py:104 msgid "i dont like this post (click again to cancel)" -msgstr "No me gusta esta entrada (clickear devuelta para cancelar)" +msgstr "" -#: templates/question.html:100 templates/question.html.py:101 +#: templates/question.html:109 templates/question.html.py:110 msgid "mark this question as favorite (click again to cancel)" -msgstr "marcar esta pregunta como favorita (clickear devuelta para cancelar)" +msgstr "" -#: templates/question.html:107 templates/question.html.py:108 +#: templates/question.html:116 templates/question.html.py:117 msgid "remove favorite mark from this question (click again to restore mark)" msgstr "" -"remover marca de favorito a esta pregunta (clickear devuelta para volver a " -"marcar)" -#: templates/question.html:134 templates/question.html.py:322 -#: templates/revisions_answer.html:53 templates/revisions_question.html:53 +#: templates/question.html:140 templates/question.html.py:294 +#: templates/revisions_answer.html:58 templates/revisions_question.html:58 msgid "edit" -msgstr "editar" - -#: templates/question.html:138 templates/question.html.py:332 -msgid "delete" -msgstr "borrar" +msgstr "" -#: templates/question.html:143 +#: templates/question.html:145 msgid "reopen" -msgstr "re-abrir" +msgstr "" -#: templates/question.html:148 +#: templates/question.html:149 msgid "close" -msgstr "cerrar" +msgstr "" -#: templates/question.html:154 templates/question.html.py:345 +#: templates/question.html:155 templates/question.html.py:300 msgid "" "report as offensive (i.e containing spam, advertising, malicious text, etc.)" msgstr "" -"reportar como ofensivo (ej. contiene spam, publicidad, texto malicioso, etc.)" -#: templates/question.html:155 templates/question.html.py:346 +#: templates/question.html:156 templates/question.html.py:301 msgid "flag offensive" -msgstr "marcar como ofensivo" - -#: templates/question.html:167 templates/question.html.py:355 -#: templates/revisions_answer.html:65 templates/revisions_question.html:65 -msgid "updated" -msgstr "actualizado" +msgstr "" -#: templates/question.html:216 templates/question.html.py:402 -#: templates/revisions_answer.html:63 templates/revisions_question.html:63 -msgid "asked" -msgstr "preguntado" +#: templates/question.html:164 templates/question.html.py:312 +msgid "delete" +msgstr "" -#: templates/question.html:246 templates/question.html.py:429 -msgid "comments" -msgstr "comentarios" +#: templates/question.html:182 templates/question.html.py:332 +msgid "delete this comment" +msgstr "" -#: templates/question.html:247 templates/question.html.py:430 +#: templates/question.html:193 templates/question.html.py:343 +#: templates/question.html:367 msgid "add comment" -msgstr "agregar comentario" +msgstr "" + +#: templates/question.html:197 +#, python-format +msgid "" +"\n" +" see <strong>one</strong> more \n" +" " +msgid_plural "" +"\n" +" see <strong>%(counter)s</strong> " +"more\n" +" " +msgstr[0] "" +msgstr[1] "" + +#: templates/question.html:203 +#, python-format +msgid "" +"\n" +" see <strong>one</strong> more " +"comment\n" +" " +msgid_plural "" +"\n" +" see <strong>%(counter)s</strong> " +"more comments\n" +" " +msgstr[0] "" +msgstr[1] "" -#: templates/question.html:260 +#: templates/question.html:219 #, python-format msgid "" -"The question has been closed for the following reason \"%(question." -"get_close_reason_display)s\" by" +"The question has been closed for the following reason \"%(close_reason)s\" by" msgstr "" -"La pregunta ha sido cerrada por el siguiente motivo \"%(question." -"get_close_reason_display)s\" por" -#: templates/question.html:262 +#: templates/question.html:221 #, python-format -msgid "close date %(question.closed_at)s" -msgstr "fecha de cerrada %(question.closed_at)s" +msgid "close date %(closed_at)s" +msgstr "" -#: templates/question.html:269 templates/user_stats.html:28 -msgid "Answers" -msgstr "Respuestas" +#: templates/question.html:229 +#, python-format +msgid "" +"\n" +" One Answer:\n" +" " +msgid_plural "" +"\n" +" %(counter)s Answers:\n" +" " +msgstr[0] "" +msgstr[1] "" -#: templates/question.html:271 +#: templates/question.html:237 msgid "oldest answers will be shown first" -msgstr "la respuesta mas vieja será mostrada primero" +msgstr "" -#: templates/question.html:271 +#: templates/question.html:237 msgid "oldest answers" -msgstr "pregunta más vieja" +msgstr "" -#: templates/question.html:272 +#: templates/question.html:239 msgid "newest answers will be shown first" -msgstr "preguntas más nuevas serán mostradas primero" +msgstr "" -#: templates/question.html:272 +#: templates/question.html:239 msgid "newest answers" -msgstr "más nuevas" +msgstr "" -#: templates/question.html:273 +#: templates/question.html:241 msgid "most voted answers will be shown first" -msgstr "las preguntas más votadas serán mostradas primero" +msgstr "" -#: templates/question.html:273 +#: templates/question.html:241 msgid "popular answers" -msgstr "respuestas populares" +msgstr "" -#: templates/question.html:287 templates/question.html.py:288 +#: templates/question.html:255 templates/question.html.py:256 msgid "i like this answer (click again to cancel)" -msgstr "me gusta esta respuesta (clickear devuelta para cancelar)" +msgstr "" -#: templates/question.html:294 templates/question.html.py:295 +#: templates/question.html:262 templates/question.html.py:263 msgid "i dont like this answer (click again to cancel)" -msgstr "no me gusta esta respuesta (clickear devuelta para cancelar)" +msgstr "" -#: templates/question.html:300 templates/question.html.py:301 +#: templates/question.html:268 templates/question.html.py:269 msgid "mark this answer as favorite (click again to undo)" -msgstr "marcar esta respuesta como favorita (clickear devuelta para deshacer)" +msgstr "" -#: templates/question.html:306 templates/question.html.py:307 +#: templates/question.html:274 templates/question.html.py:275 msgid "the author of the question has selected this answer as correct" -msgstr "el autor de esta pregunta ha seleccionado esta respuesta como correcta" - -#: templates/question.html:329 -msgid "undelete" -msgstr "deshacer eliminar" +msgstr "" -#: templates/question.html:339 +#: templates/question.html:288 msgid "answer permanent link" -msgstr "enlace permanente a respuesta" +msgstr "" -#: templates/question.html:340 +#: templates/question.html:289 msgid "permanent link" -msgstr "enlace permanente" +msgstr "" + +#: templates/question.html:312 +msgid "undelete" +msgstr "" + +#: templates/question.html:347 +#, python-format +msgid "" +"\n" +" see <strong>one</" +"strong> more \n" +" " +msgid_plural "" +"\n" +" see <strong>%" +"(counter)s</strong> more\n" +" " +msgstr[0] "" +msgstr[1] "" + +#: templates/question.html:353 +#, python-format +msgid "" +"\n" +" see <strong>one</" +"strong> more comment\n" +" " +msgid_plural "" +"\n" +" see <strong>%" +"(counter)s</strong> more comments\n" +" " +msgstr[0] "" +msgstr[1] "" + +#: templates/question.html:366 +msgid "comments" +msgstr "" + +#: templates/question.html:386 templates/question.html.py:389 +msgid "Notify me once a day when there are any new answers" +msgstr "" + +#: templates/question.html:392 +msgid "Notify me weekly when there are any new answers" +msgstr "" + +#: templates/question.html:397 +#, python-format +msgid "" +"\n" +" You can always adjust frequency of email updates from your %" +"(profile_url)s\n" +" " +msgstr "" -#: templates/question.html:453 +#: templates/question.html:404 +msgid "once you sign in you will be able to subscribe for any updates here" +msgstr "" + +#: templates/question.html:415 msgid "Your answer" -msgstr "Tu respuesta" +msgstr "" -#: templates/question.html:456 +#: templates/question.html:417 +msgid "Be the first one to answer this question!" +msgstr "" + +#: templates/question.html:423 msgid "you can answer anonymously and then login" -msgstr "puedes responder de forma anónima y luego ingresar" +msgstr "" -#: templates/question.html:479 -msgid "Answer the question" -msgstr "Responde la pregunta" +#: templates/question.html:427 +msgid "answer your own question only to give an answer" +msgstr "" -#: templates/question.html:481 -msgid "Notify me daily if there are any new answers." -msgstr "Notificarme diariamente si hay nuevas respuestas." +#: templates/question.html:429 +msgid "please only give an answer, no discussions" +msgstr "" -#: templates/question.html:483 -msgid "once you sign in you will be able to subscribe for any updates here" +#: templates/question.html:465 +msgid "Login/Signup to Post Your Answer" +msgstr "" + +#: templates/question.html:468 +msgid "Answer Your Own Question" msgstr "" -"una vez que hayas ingresado podrás suscribirte a cualquiera de las " -"actualizaciones aquí." -#: templates/question.html:494 +#: templates/question.html:470 +msgid "Answer the question" +msgstr "" + +#: templates/question.html:483 msgid "Question tags" -msgstr "Tags de la pregunta" +msgstr "" -#: templates/question.html:504 +#: templates/question.html:493 msgid "question asked" -msgstr "pregunta preguntada" - -#: templates/question.html:504 templates/question.html.py:510 -#: templates/user_info.html:51 -msgid "ago" -msgstr " atras" +msgstr "" -#: templates/question.html:507 +#: templates/question.html:496 msgid "question was seen" -msgstr "la pregunta fue vista" +msgstr "" -#: templates/question.html:507 +#: templates/question.html:496 msgid "times" -msgstr "veces" +msgstr "" -#: templates/question.html:510 +#: templates/question.html:499 msgid "last updated" -msgstr "última vez actualizada" +msgstr "" -#: templates/question.html:515 +#: templates/question.html:504 msgid "Related questions" -msgstr "Preguntas relacionadas" +msgstr "" -#: templates/question_edit.html:4 templates/question_edit.html.py:65 +#: templates/question_edit.html:5 templates/question_edit.html.py:66 msgid "Edit question" -msgstr "Editar pregunta" +msgstr "" #: templates/question_edit_tips.html:4 msgid "question tips" -msgstr "sugerencias sobre pregunta" +msgstr "" #: templates/question_edit_tips.html:7 msgid "please ask a relevant question" -msgstr "por favor hacer preguntas relevantes" +msgstr "" #: templates/question_edit_tips.html:10 msgid "please try provide enough details" -msgstr "intente proveer suficientes detalles" +msgstr "" -#: templates/question_retag.html:3 templates/question_retag.html.py:52 +#: templates/question_retag.html:4 templates/question_retag.html.py:53 msgid "Change tags" -msgstr "Cambiar etiquetas" +msgstr "" -#: templates/question_retag.html:39 +#: templates/question_retag.html:40 msgid "up to 5 tags, less than 20 characters each" -msgstr "hasta 5 etiquetas, menos de 20 caracteres cada una" +msgstr "" -#: templates/question_retag.html:82 +#: templates/question_retag.html:83 msgid "Why use and modify tags?" -msgstr "¿Porqué usar y modificar etiquetas?" +msgstr "" -#: templates/question_retag.html:85 +#: templates/question_retag.html:86 msgid "tags help us keep Questions organized" -msgstr "las etiquetas nos permiten mantener las Preguntas organizadas" +msgstr "" -#: templates/question_retag.html:91 +#: templates/question_retag.html:94 msgid "tag editors receive special awards from the community" msgstr "" -"los editores de etiquetas reciben distinciones especiales de la comunidad" -#: templates/questions.html:23 +#: templates/questions.html:28 templates/questions.html.py:32 msgid "Found by tags" -msgstr "Encontradas por etiqueta" +msgstr "" -#: templates/questions.html:23 +#: templates/questions.html:28 templates/questions.html.py:38 msgid "Found by title" -msgstr "Encontradas por título" +msgstr "" -#: templates/questions.html:23 +#: templates/questions.html:28 templates/questions.html.py:44 msgid "All questions" -msgstr "Todas las preguntas" +msgstr "" + +#: templates/questions.html:36 +msgid "Search results" +msgstr "" + +#: templates/questions.html:42 templates/unanswered.html:8 +#: templates/unanswered.html.py:19 +msgid "Unanswered questions" +msgstr "" -#: templates/questions.html:25 templates/unanswered.html:20 +#: templates/questions.html:51 templates/unanswered.html:21 msgid "most recently asked questions" -msgstr "preguntas hechas más recientemente" +msgstr "" -#: templates/questions.html:26 -msgid "most recently updated questions" -msgstr "preguntas actualizadas más recientemente" +#: templates/questions.html:143 +msgid "Category: " +msgstr "" -#: templates/questions.html:26 -msgid "active" -msgstr "actividad" +#: templates/questions.html:149 +msgid "Did not find anything?" +msgstr "" + +#: templates/questions.html:152 +msgid "Did not find what you were looking for?" +msgstr "" + +#: templates/questions.html:154 +msgid "Please, post your question!" +msgstr "" -#: templates/questions.html:109 +#: templates/questions.html:169 #, python-format msgid "" "\n" @@ -1889,15 +2300,9 @@ msgid_plural "" "\t\t\thave total %(q_num)s questions tagged %(tagname)s\n" "\t\t\t" msgstr[0] "" -"\n" -"\t\t\ttiene un total de %(q_num)s preguntas etiquetadas con %(tagname)s\n" -"\t\t\t" msgstr[1] "" -"\n" -"\t\t\ttiene un total de %(q_num)s preguntas etiquetadas con %(tagname)s\n" -"\t\t\t" -#: templates/questions.html:116 +#: templates/questions.html:176 #, python-format msgid "" "\n" @@ -1908,16 +2313,10 @@ msgid_plural "" "\t\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n" "\t\t\t\t" msgstr[0] "" -"\n" -"\t\t\thay un total de %(q_num)s preguntas que contienen %(searchtitle)s\n" -"\t\t\t" msgstr[1] "" -"\n" -"\t\t\thay un total de %(q_num)s pregunta que contiene %(searchtitle)s\n" -"\t\t\t" -#: templates/questions.html:122 -#, fuzzy, python-format +#: templates/questions.html:182 +#, python-format msgid "" "\n" "\t\t\t\thave total %(q_num)s questions\n" @@ -1926,628 +2325,750 @@ msgid_plural "" "\n" "\t\t\t\thave total %(q_num)s questions\n" "\t\t\t\t" -msgstr[0] "ver preguntas etiquetadas '%(tagname)s'" -msgstr[1] "ver pregunta etiquetada '%(tagname)s'" +msgstr[0] "" +msgstr[1] "" + +#: templates/questions.html:191 +#, python-format +msgid "" +"\n" +" have total %(q_num)s questions tagged %(tagname)s\n" +" " +msgid_plural "" +"\n" +" have total %(q_num)s questions tagged %(tagname)s\n" +" " +msgstr[0] "" +msgstr[1] "" + +#: templates/questions.html:199 +#, python-format +msgid "" +"\n" +" have total %(q_num)s questions containing %(searchtitle)" +"s in full text\n" +" " +msgid_plural "" +"\n" +" have total %(q_num)s questions containing %(searchtitle)" +"s in full text\n" +" " +msgstr[0] "" +msgstr[1] "" + +#: templates/questions.html:205 +#, python-format +msgid "" +"\n" +" have total %(q_num)s questions containing %(searchtitle)" +"s\n" +" " +msgid_plural "" +"\n" +" have total %(q_num)s questions containing %(searchtitle)" +"s\n" +" " +msgstr[0] "" +msgstr[1] "" + +#: templates/questions.html:213 +#, python-format +msgid "" +"\n" +" have total %(q_num)s unanswered questions\n" +" " +msgid_plural "" +"\n" +" have total %(q_num)s unanswered questions\n" +" " +msgstr[0] "" +msgstr[1] "" + +#: templates/questions.html:219 +#, python-format +msgid "" +"\n" +" have total %(q_num)s questions\n" +" " +msgid_plural "" +"\n" +" have total %(q_num)s questions\n" +" " +msgstr[0] "" +msgstr[1] "" -#: templates/questions.html:131 +#: templates/questions.html:230 msgid "latest questions info" -msgstr "<strong>Más recientes</strong> preguntas son mostradas primero." +msgstr "" -#: templates/questions.html:135 +#: templates/questions.html:234 msgid "Questions are sorted by the <strong>time of last update</strong>." msgstr "" -"Las preguntas estan ordenadas por <strong>fecha de último update</strong>." -#: templates/questions.html:136 +#: templates/questions.html:235 msgid "Most recently answered ones are shown first." -msgstr "Las más recientemente respondidas son mostradas primero." +msgstr "" -#: templates/questions.html:140 +#: templates/questions.html:239 msgid "Questions sorted by <strong>number of responses</strong>." -msgstr "Preguntas ordenadas por <strong>número de respuestas</strong>." +msgstr "" -#: templates/questions.html:141 +#: templates/questions.html:240 msgid "Most answered questions are shown first." -msgstr "Preguntas más respondidas aparecen primero." +msgstr "" -#: templates/questions.html:145 +#: templates/questions.html:244 msgid "Questions are sorted by the <strong>number of votes</strong>." -msgstr "Las preguntas son ordenadas por el <strong>número de votos</strong>." +msgstr "" -#: templates/questions.html:146 +#: templates/questions.html:245 msgid "Most voted questions are shown first." -msgstr "Las preguntas más votadas son mostradas primero." +msgstr "" -#: templates/questions.html:153 templates/unanswered.html:102 +#: templates/questions.html:253 templates/unanswered.html:118 msgid "Related tags" -msgstr "Etiquetas relacionadas" +msgstr "" + +#: templates/questions.html:263 templates/tag_selector.html:10 +#: templates/tag_selector.html.py:27 +#, python-format +msgid "see questions tagged '%(tag_name)s'" +msgstr "" #: templates/reopen.html:6 templates/reopen.html.py:16 msgid "Reopen question" -msgstr "Re-abrir pregunta" +msgstr "" #: templates/reopen.html:19 msgid "Open the previously closed question" -msgstr "Abrir pregunta previamente cerrada" +msgstr "" #: templates/reopen.html:22 msgid "The question was closed for the following reason " -msgstr "La pregunta fue cerrada por el siguiente motivo " +msgstr "" #: templates/reopen.html:22 msgid "reason - leave blank in english" -msgstr "razón - " +msgstr "" #: templates/reopen.html:22 msgid "on " -msgstr "el " +msgstr "" #: templates/reopen.html:22 msgid "date closed" -msgstr "fecha cerrada" +msgstr "" #: templates/reopen.html:29 msgid "Reopen this question" -msgstr "Re-abrir esta pregunta" +msgstr "" -#: templates/revisions_answer.html:7 templates/revisions_answer.html.py:36 -#: templates/revisions_question.html:8 templates/revisions_question.html:36 +#: templates/revisions_answer.html:7 templates/revisions_answer.html.py:38 +#: templates/revisions_question.html:8 templates/revisions_question.html:38 msgid "Revision history" -msgstr "Historial de revisiones" +msgstr "" + +#: templates/revisions_answer.html:50 templates/revisions_question.html:50 +msgid "click to hide/show revision" +msgstr "" + +#: templates/tag_selector.html:4 +msgid "Interesting tags" +msgstr "" + +#: templates/tag_selector.html:14 +#, python-format +msgid "remove '%(tag_name)s' from the list of interesting tags" +msgstr "" + +#: templates/tag_selector.html:20 templates/tag_selector.html.py:37 +msgid "Add" +msgstr "" + +#: templates/tag_selector.html:21 +msgid "Ignored tags" +msgstr "" + +#: templates/tag_selector.html:31 +#, python-format +msgid "remove '%(tag_name)s' from the list of ignored tags" +msgstr "" + +#: templates/tag_selector.html:40 +msgid "keep ingored questions hidden" +msgstr "" #: templates/tags.html:6 templates/tags.html.py:30 msgid "Tag list" -msgstr "Lista de etiquetas" +msgstr "" #: templates/tags.html:32 msgid "sorted alphabetically" -msgstr "ordenar alfabéticamente" +msgstr "" #: templates/tags.html:32 msgid "by name" -msgstr "por nombre" +msgstr "" #: templates/tags.html:33 msgid "sorted by frequency of tag use" -msgstr "ordenar por frecuencia de uso de la etiqueta" +msgstr "" #: templates/tags.html:33 msgid "by popularity" -msgstr "por popularidad" +msgstr "" #: templates/tags.html:39 msgid "All tags matching query" -msgstr "Todas las etiquetas que coincidan con la busqueda" +msgstr "" #: templates/tags.html:39 msgid "all tags - make this empty in english" -msgstr "todas las tags" +msgstr "" #: templates/tags.html:42 msgid "Nothing found" -msgstr "Nada encontrado" - -#: templates/unanswered.html:7 templates/unanswered.html.py:18 -msgid "Unanswered questions" -msgstr "Preguntas sin respuesta" +msgstr "" -#: templates/unanswered.html:97 +#: templates/unanswered.html:114 #, python-format msgid "have %(num_q)s unanswered questions" msgstr "" -"<div class=\"questions-count\">%(num_q)s</div> preguntas <strong>sin " -"respuesta</strong> " #: templates/user_edit.html:6 msgid "Edit user profile" -msgstr "Editar perfil de usuario" +msgstr "" #: templates/user_edit.html:19 msgid "edit profile" -msgstr "editar perfil" +msgstr "" #: templates/user_edit.html:31 msgid "image associated with your email address" -msgstr "imagen asociada con tu email" +msgstr "" #: templates/user_edit.html:31 -msgid "avatar" -msgstr "avatar" +#, python-format +msgid "avatar, see %(gravatar_faq_url)s" +msgstr "" -#: templates/user_edit.html:36 templates/user_info.html:31 +#: templates/user_edit.html:36 templates/user_info.html:56 msgid "Registered user" -msgstr "Usuario registrado" +msgstr "" -#: templates/user_edit.html:82 +#: templates/user_edit.html:86 templates/user_email_subscriptions.html:23 msgid "Update" -msgstr "Actualización" +msgstr "" + +#: templates/user_email_subscriptions.html:8 +msgid "Email subscription settings" +msgstr "" -#: templates/user_info.html:34 +#: templates/user_email_subscriptions.html:9 +msgid "email subscription settings info" +msgstr "" + +#: templates/user_email_subscriptions.html:24 +msgid "Stop sending email" +msgstr "" + +#: templates/user_info.html:22 +msgid "karma" +msgstr "" + +#: templates/user_info.html:32 +msgid "Moderate this user" +msgstr "" + +#: templates/user_info.html:45 msgid "update profile" -msgstr "actualizar perfil de usuario" +msgstr "" -#: templates/user_info.html:40 +#: templates/user_info.html:60 msgid "real name" -msgstr "nombre real" +msgstr "" -#: templates/user_info.html:45 +#: templates/user_info.html:65 msgid "member for" -msgstr "miembro de" +msgstr "" -#: templates/user_info.html:50 +#: templates/user_info.html:70 msgid "last seen" -msgstr "última vez visto" +msgstr "" -#: templates/user_info.html:56 +#: templates/user_info.html:76 msgid "user website" -msgstr "sitio web del usuario" +msgstr "" -#: templates/user_info.html:62 +#: templates/user_info.html:82 msgid "location" -msgstr "ubicación" +msgstr "" -#: templates/user_info.html:69 +#: templates/user_info.html:89 msgid "age" -msgstr "edad" +msgstr "" -#: templates/user_info.html:70 +#: templates/user_info.html:90 msgid "age unit" -msgstr "unidad de edad" +msgstr "" -#: templates/user_info.html:76 +#: templates/user_info.html:96 msgid "todays unused votes" -msgstr "votos de hoy no usados" +msgstr "" -#: templates/user_info.html:77 +#: templates/user_info.html:97 msgid "votes left" -msgstr "votos restantes" - -#: templates/user_preferences.html:10 -msgid "Connect with Twitter" -msgstr "Conectar con Twitter" - -#: templates/user_preferences.html:13 -msgid "Twitter account name:" -msgstr "Nombre de usuario en Twitter:" - -#: templates/user_preferences.html:15 -msgid "Twitter password:" -msgstr "Contraseña de Twitter:" - -#: templates/user_preferences.html:17 -msgid "Send my Questions to Twitter" -msgstr "Enviar mis preguntas a Twitter" - -#: templates/user_preferences.html:18 -msgid "Send my Answers to Twitter" -msgstr "Enviar mis respuestas a Twitter" +msgstr "" -#: templates/user_preferences.html:19 -msgid "Save" -msgstr "Guardar" +#: templates/user_stats.html:12 +#, python-format +msgid "" +"\n" +" <span class=\"count\">1</span> Question\n" +" " +msgid_plural "" +"\n" +" <span class=\"count\">%(counter)s</span> Questions\n" +" " +msgstr[0] "" +msgstr[1] "" -#: templates/user_stats.html:16 -msgid "User questions" -msgstr "Preguntas del usuario" +#: templates/user_stats.html:23 +#, python-format +msgid "" +"\n" +" <span class=\"count\">1</span> Answer\n" +" " +msgid_plural "" +"\n" +" <span class=\"count\">%(counter)s</span> Answers\n" +" " +msgstr[0] "" +msgstr[1] "" -#: templates/user_stats.html:38 +#: templates/user_stats.html:36 #, python-format msgid "the answer has been voted for %(vote_count)s times" -msgstr "la respuesta ha sido votada %(vote_count)s veces" +msgstr "" -#: templates/user_stats.html:38 +#: templates/user_stats.html:36 msgid "this answer has been selected as correct" -msgstr "esta respuesta ha sido seleccionada como correcta" +msgstr "" #: templates/user_stats.html:46 #, python-format -msgid "the answer has been commented %(comment_count)s times" -msgstr "la respuesta ha sido comentada %(comment_count)s veces" +msgid "" +"\n" +" (one comment)\n" +" " +msgid_plural "" +"\n" +" the answer has been commented %(comment_count)s times\n" +" " +msgstr[0] "" +msgstr[1] "" + +#: templates/user_stats.html:61 +#, python-format +msgid "" +"\n" +" <span class=\"count\">1</span> Vote\n" +" " +msgid_plural "" +"\n" +" <span class=\"count\">%(cnt)s</span> Votes\n" +" " +msgstr[0] "" +msgstr[1] "" -#: templates/user_stats.html:60 -msgid "votes total" -msgstr "votos totales" +#: templates/user_stats.html:72 +msgid "thumb up" +msgstr "" -#: templates/user_stats.html:69 +#: templates/user_stats.html:73 msgid "user has voted up this many times" -msgstr "el usuario ha votado positivo esta cantidad de veces" +msgstr "" -#: templates/user_stats.html:74 +#: templates/user_stats.html:77 +msgid "thumb down" +msgstr "" + +#: templates/user_stats.html:78 msgid "user voted down this many times" -msgstr "el usuario voto negativo esta cantidad de veces" +msgstr "" #: templates/user_stats.html:87 -msgid "Tags" -msgstr "Etiquetas" +#, python-format +msgid "" +"\n" +" <span class=\"count\">1</span> Tag\n" +" " +msgid_plural "" +"\n" +" <span class=\"count\">%(counter)s</span> Tags\n" +" " +msgstr[0] "" +msgstr[1] "" -#: templates/user_stats.html:97 +#: templates/user_stats.html:100 #, python-format -msgid "see other questions tagged '%(tag)s' " -msgstr "ver otras preguntas etiqueteadas '%(tag)s'" +msgid "" +"see other questions with %(view_user)s's contributions tagged '%(tag_name)s' " +msgstr "" + +#: templates/user_stats.html:115 +#, python-format +msgid "" +"\n" +" <span class=\"count\">1</span> Badge\n" +" " +msgid_plural "" +"\n" +" <span class=\"count\">%(counter)s</span> Badges\n" +" " +msgstr[0] "" +msgstr[1] "" #: templates/user_tabs.html:7 msgid "User profile" -msgstr "Perfil de usuario" +msgstr "" #: templates/user_tabs.html:16 msgid "graph of user reputation" -msgstr "gráfica de la reputación del usuario" +msgstr "" #: templates/user_tabs.html:17 msgid "reputation history" -msgstr "historial de reputación" +msgstr "" + +#: templates/user_tabs.html:23 +msgid "questions that user selected as his/her favorite" +msgstr "" #: templates/user_tabs.html:24 msgid "favorites" -msgstr "favoritos" - -#: templates/user_tabs.html:28 -msgid "settings" -msgstr "preferencias" +msgstr "" #: templates/users.html:6 templates/users.html.py:24 msgid "Users" -msgstr "Usuarios" +msgstr "" #: templates/users.html:27 msgid "recent" -msgstr "reciente" +msgstr "" #: templates/users.html:28 msgid "oldest" -msgstr "más viejo" +msgstr "" #: templates/users.html:29 msgid "by username" -msgstr "por nombre de usuario" +msgstr "" #: templates/users.html:35 #, python-format msgid "users matching query %(suser)s:" -msgstr "usuarios que coincidan con la busqueda %(suser)s:" +msgstr "" #: templates/users.html:39 msgid "Nothing found." -msgstr "Nada encontrado." +msgstr "" #: templates/users_questions.html:11 msgid "this questions was selected as favorite" -msgstr "esta pregunta ha sido seleccionada como favorita" +msgstr "" + +#: templates/users_questions.html:12 +msgid "thumb-up on" +msgstr "" -#: templates/users_questions.html:33 +#: templates/users_questions.html:19 +msgid "thumb-up off" +msgstr "" + +#: templates/users_questions.html:34 msgid "this answer has been accepted to be correct" -msgstr "esta respuesta ha sido aceptada como correcta" +msgstr "" -#: templates/authopenid/changeemail.html:7 -#: templates/authopenid/changeemail.html:33 +#: templates/authopenid/changeemail.html:3 +#: templates/authopenid/changeemail.html:9 +#: templates/authopenid/changeemail.html:38 msgid "Change email" -msgstr "Cambiar dirección email" +msgstr "" -#: templates/authopenid/changeemail.html:10 +#: templates/authopenid/changeemail.html:11 +msgid "Save your email address" +msgstr "" + +#: templates/authopenid/changeemail.html:16 #, python-format msgid "change %(email)s info" -msgstr "Cambiar información del correo electrónico %(email)s" +msgstr "" -#: templates/authopenid/changeemail.html:13 -#: templates/authopenid/changeopenid.html:14 -#: templates/authopenid/changepw.html:19 templates/authopenid/delete.html:15 -#: templates/authopenid/delete.html:25 -msgid "Please correct errors below:" -msgstr "Por favor corrija los errores debajo: " +#: templates/authopenid/changeemail.html:18 +#, python-format +msgid "here is why email is required, see %(gravatar_faq_url)s" +msgstr "" -#: templates/authopenid/changeemail.html:30 +#: templates/authopenid/changeemail.html:31 msgid "Your new Email" -msgstr "Tu nuevo Email" +msgstr "" #: templates/authopenid/changeemail.html:31 -#: templates/authopenid/signin.html:136 -msgid "Password" -msgstr "Contraseña" +msgid "Your Email" +msgstr "" -#: templates/authopenid/changeemail.html:42 -#, fuzzy +#: templates/authopenid/changeemail.html:38 +msgid "Save Email" +msgstr "" + +#: templates/authopenid/changeemail.html:49 msgid "Validate email" -msgstr "Cambiar dirección email" +msgstr "" -#: templates/authopenid/changeemail.html:45 +#: templates/authopenid/changeemail.html:52 #, python-format -msgid "validate %(email)s info" -msgstr "validar información de %(email)s " +msgid "validate %(email)s info or go to %(change_email_url)s" +msgstr "" -#: templates/authopenid/changeemail.html:50 +#: templates/authopenid/changeemail.html:57 msgid "Email not changed" -msgstr "Email no modificado." +msgstr "" -#: templates/authopenid/changeemail.html:53 +#: templates/authopenid/changeemail.html:60 #, python-format -msgid "old %(email)s kept" -msgstr "se ha conservado el viejo email %(email)s " +msgid "old %(email)s kept, if you like go to %(change_email_url)s" +msgstr "" -#: templates/authopenid/changeemail.html:58 +#: templates/authopenid/changeemail.html:65 msgid "Email changed" -msgstr "Email modificado." +msgstr "" -#: templates/authopenid/changeemail.html:61 +#: templates/authopenid/changeemail.html:68 #, python-format msgid "your current %(email)s can be used for this" -msgstr "tu email actual %(email)s puede ser usado para esto" +msgstr "" -#: templates/authopenid/changeemail.html:66 +#: templates/authopenid/changeemail.html:73 msgid "Email verified" -msgstr "Email verificado" +msgstr "" -#: templates/authopenid/changeemail.html:69 +#: templates/authopenid/changeemail.html:76 msgid "thanks for verifying email" -msgstr "gracias por verificar su correo" +msgstr "" -#: templates/authopenid/changeemail.html:74 +#: templates/authopenid/changeemail.html:81 msgid "email key not sent" -msgstr "llave de correo no enviada" +msgstr "" -#: templates/authopenid/changeemail.html:77 +#: templates/authopenid/changeemail.html:84 #, python-format msgid "email key not sent %(email)s change email here %(change_link)s" -msgstr "email no enviado %(email)s cambiar email aquí %(change_link)s" +msgstr "" + +#: templates/authopenid/changeopenid.html:4 +#: templates/authopenid/changeopenid.html:30 +#: templates/authopenid/settings.html:34 +msgid "Change OpenID" +msgstr "" #: templates/authopenid/changeopenid.html:8 msgid "Account: change OpenID URL" -msgstr "Cuenta: cambiar la URL de OpenID" +msgstr "" #: templates/authopenid/changeopenid.html:12 msgid "" "This is where you can change your OpenID URL. Make sure you remember it!" -msgstr "Aquí es donde puedes cambiar tu OpenID URL. Asegurate de recordarla!" +msgstr "" + +#: templates/authopenid/changeopenid.html:14 +#: templates/authopenid/delete.html:14 templates/authopenid/delete.html:24 +msgid "Please correct errors below:" +msgstr "" #: templates/authopenid/changeopenid.html:29 msgid "OpenID URL:" -msgstr "URL de OpenID:" +msgstr "" -#: templates/authopenid/changeopenid.html:30 -msgid "Change OpenID" -msgstr "Cambiar OpenID" +#: templates/authopenid/changepw.html:5 templates/authopenid/changepw.html:14 +#: templates/authopenid/settings.html:29 +msgid "Change password" +msgstr "" -#: templates/authopenid/changepw.html:14 +#: templates/authopenid/changepw.html:7 msgid "Account: change password" -msgstr "Cuenta: cambiar contraseña" +msgstr "" -#: templates/authopenid/changepw.html:17 +#: templates/authopenid/changepw.html:8 msgid "This is where you can change your password. Make sure you remember it!" -msgstr "Aquí es donde puedes cambiar tu contraseña. Asegurate de recordarlo!" - -#: templates/authopenid/changepw.html:27 -msgid "Current password" -msgstr "Contraseña actual" - -#: templates/authopenid/changepw.html:28 -msgid "New password" -msgstr "Nueva contraseña" - -#: templates/authopenid/changepw.html:29 -msgid "New password again" -msgstr "Nueva contraseña nuevamente" - -#: templates/authopenid/changepw.html:30 templates/authopenid/settings.html:29 -msgid "Change password" -msgstr "Cambiar contraseña" +msgstr "" -#: templates/authopenid/complete.html:5 +#: templates/authopenid/complete.html:19 msgid "Connect your OpenID with this site" -msgstr "Vincular tu OpenID con este sitio" +msgstr "" -#: templates/authopenid/complete.html:8 +#: templates/authopenid/complete.html:22 msgid "Connect your OpenID with your account on this site" -msgstr "Vincular tu OpenID con tu cuenta en este sitio" +msgstr "" + +#: templates/authopenid/complete.html:27 +#, python-format +msgid "register new %(provider)s account info, see %(gravatar_faq_url)s" +msgstr "" + +#: templates/authopenid/complete.html:31 +#, python-format +msgid "" +"%(username)s already exists, choose another name for \n" +" %(provider)s. Email is required too, see %" +"(gravatar_faq_url)s\n" +" " +msgstr "" -#: templates/authopenid/complete.html:12 +#: templates/authopenid/complete.html:35 #, python-format -msgid "register new %(provider)s account info" -msgstr "Registrar una nueva cuenta %(provider)s." +msgid "" +"register new external %(provider)s account info, see %(gravatar_faq_url)s" +msgstr "" -#: templates/authopenid/complete.html:14 +#: templates/authopenid/complete.html:40 msgid "This account already exists, please use another." -msgstr "Esta cuenta ya existe, por favor usar otra." +msgstr "" -#: templates/authopenid/complete.html:19 templates/authopenid/complete.html:32 -#: templates/authopenid/signin.html:120 +#: templates/authopenid/complete.html:55 msgid "Sorry, looks like we have some errors:" -msgstr "Ups, parece que hay errores:" +msgstr "" -#: templates/authopenid/complete.html:47 +#: templates/authopenid/complete.html:76 msgid "Screen name label" -msgstr "Nombre de Usuario" +msgstr "" -#: templates/authopenid/complete.html:48 +#: templates/authopenid/complete.html:83 msgid "Email address label" -msgstr "Su email (correo electrónico)" +msgstr "" + +#: templates/authopenid/complete.html:89 templates/authopenid/signup.html:15 +msgid "receive updates motivational blurb" +msgstr "" + +#: templates/authopenid/complete.html:93 +msgid "Tag filter tool will be your right panel, once you log in." +msgstr "" -#: templates/authopenid/complete.html:49 +#: templates/authopenid/complete.html:95 msgid "create account" -msgstr "crear cuenta" +msgstr "" -#: templates/authopenid/complete.html:56 +#: templates/authopenid/complete.html:104 msgid "Existing account" -msgstr "Cuenta existente" +msgstr "" -#: templates/authopenid/complete.html:57 +#: templates/authopenid/complete.html:105 msgid "user name" -msgstr "nombre de usuario" +msgstr "" -#: templates/authopenid/complete.html:58 +#: templates/authopenid/complete.html:106 msgid "password" -msgstr "contraseña" - -#: templates/authopenid/complete.html:61 -msgid "Register" -msgstr "Registrarse" - -#: templates/authopenid/complete.html:62 templates/authopenid/signin.html:138 -msgid "Forgot your password?" -msgstr "¿Olvidaste tu contraseña?" - -#: templates/authopenid/confirm_email.txt:2 -msgid "Thank you for registering at our Q&A forum!" msgstr "" -#: templates/authopenid/confirm_email.txt:4 -msgid "Your account details are:" +#: templates/authopenid/complete.html:111 +msgid "Register" msgstr "" -#: templates/authopenid/confirm_email.txt:6 -#: templates/authopenid/sendpw_email.txt:7 -msgid "Username:" -msgstr "Nombre de usuario:" - -#: templates/authopenid/confirm_email.txt:7 -#: templates/authopenid/delete.html:20 -msgid "Password:" -msgstr "Contraseña" - -#: templates/authopenid/confirm_email.txt:9 -msgid "Please sign in here:" +#: templates/authopenid/complete.html:112 templates/authopenid/signin.html:140 +msgid "Forgot your password?" msgstr "" -#: templates/authopenid/confirm_email.txt:12 -#: templates/authopenid/email_validation.txt:14 -#: templates/authopenid/sendpw_email.txt:13 -msgid "" -"Sincerely,\n" -"Forum Administrator" +#: templates/authopenid/delete.html:4 templates/authopenid/settings.html:38 +msgid "Delete account" msgstr "" -#: templates/authopenid/delete.html:9 +#: templates/authopenid/delete.html:8 msgid "Account: delete account" -msgstr "Cuenta: borrar cuenta" +msgstr "" -#: templates/authopenid/delete.html:13 +#: templates/authopenid/delete.html:12 msgid "" "Note: After deleting your account, anyone will be able to register this " "username." msgstr "" -"Nota: Luego de borrar tu cuenta, cualquiera va a poder registrarse con este " -"nombre de usuario." -#: templates/authopenid/delete.html:17 +#: templates/authopenid/delete.html:16 msgid "Check confirm box, if you want delete your account." -msgstr "Marca caja de confirmación, si deseas borrar tu cuenta." +msgstr "" -#: templates/authopenid/delete.html:32 +#: templates/authopenid/delete.html:19 +msgid "Password:" +msgstr "" + +#: templates/authopenid/delete.html:31 msgid "I am sure I want to delete my account." -msgstr "Estoy seguro que quiero borrar mi cuenta." +msgstr "" -#: templates/authopenid/delete.html:33 +#: templates/authopenid/delete.html:32 msgid "Password/OpenID URL" -msgstr "Contraseña/OpenID URL" +msgstr "" -#: templates/authopenid/delete.html:33 +#: templates/authopenid/delete.html:32 msgid "(required for your security)" -msgstr "(requerido por tu seguridad)" - -#: templates/authopenid/delete.html:35 -msgid "Delete account permanently" -msgstr "Borrar la cuenta de forma permanente" - -#: templates/authopenid/email_validation.txt:2 -msgid "Greetings from the Q&A forum" msgstr "" -#: templates/authopenid/email_validation.txt:4 -msgid "To make use of the Forum, please follow the link below:" +#: templates/authopenid/delete.html:34 +msgid "Delete account permanently" msgstr "" -#: templates/authopenid/email_validation.txt:8 -msgid "Following the link above will help us verify your email address." +#: templates/authopenid/external_legacy_login_info.html:4 +#: templates/authopenid/external_legacy_login_info.html:7 +msgid "Traditional login information" msgstr "" -#: templates/authopenid/email_validation.txt:10 -msgid "" -"If you beleive that this message was sent in mistake - \n" -"no further action is needed. Just ingore this email, we apologize\n" -"for any inconvenience" +#: templates/authopenid/external_legacy_login_info.html:17 +msgid "how to login with password through external login website" msgstr "" -#: templates/authopenid/sendpw.html:4 templates/authopenid/sendpw.html.py:8 +#: templates/authopenid/sendpw.html:4 templates/authopenid/sendpw.html.py:7 msgid "Send new password" -msgstr "Enviar nueva contraseña" - -#: templates/authopenid/sendpw.html:12 -msgid "Lost your password? No problem - here you can reset it." -msgstr "¿Haz perdido tu contraseña? No hay problema - aquí puedes re-crearla." - -#: templates/authopenid/sendpw.html:13 -msgid "" -"Please enter your username below and new password will be sent to your " -"registered e-mail" msgstr "" -"Por favor, ingresa tu nombre de usuario y una nueva contraseña será enviada " -"a la dirección de email registrada." - -#: templates/authopenid/sendpw.html:28 -msgid "User name" -msgstr "Nombre de usuario" - -#: templates/authopenid/sendpw.html:30 -msgid "Reset password" -msgstr "Re-crear contraseña" - -#: templates/authopenid/sendpw.html:30 -msgid "return to login" -msgstr "volver a 'Ingresar'" -#: templates/authopenid/sendpw.html:33 -msgid "" -"Note: your new password will be activated only after you click the " -"activation link in the email message" +#: templates/authopenid/sendpw.html:10 +msgid "password recovery information" msgstr "" -"Nota: tu nueva contraseña solo será activada luego de que hagas click en el " -"link de activación en el email enviado." -#: templates/authopenid/sendpw_email.txt:2 -#, python-format -msgid "" -"Someone has requested to reset your password on %(site_url)s.\n" -"If it were not you, it is safe to ignore this email." +#: templates/authopenid/sendpw.html:21 +msgid "Reset password" msgstr "" -#: templates/authopenid/sendpw_email.txt:5 -msgid "Your new account details are:" +#: templates/authopenid/sendpw.html:22 +msgid "return to login" msgstr "" -#: templates/authopenid/sendpw_email.txt:8 -#, fuzzy -msgid "New password:" -msgstr "Nueva contraseña:" - -#: templates/authopenid/sendpw_email.txt:10 -msgid "To confirm that you wanted to reset your password please visit:" +#: templates/authopenid/settings.html:4 +msgid "Account functions" msgstr "" #: templates/authopenid/settings.html:30 msgid "Give your account a new password." -msgstr "Crea una nueva contraseña para tu cuenta." +msgstr "" #: templates/authopenid/settings.html:31 msgid "Change email " -msgstr "Cambiar email " +msgstr "" #: templates/authopenid/settings.html:32 msgid "Add or update the email address associated with your account." -msgstr "Agrega o actualiza el email asociado a tu cuenta." +msgstr "" #: templates/authopenid/settings.html:35 msgid "Change openid associated to your account" -msgstr "Cambia el OpenID asociado a tu cuenta" - -#: templates/authopenid/settings.html:38 -msgid "Delete account" -msgstr "Eliminar cuenta" +msgstr "" #: templates/authopenid/settings.html:39 msgid "Erase your username and all your data from website" -msgstr "Eliminar tu nombre de usuario y toda tu información del sitio" +msgstr "" -#: templates/authopenid/signin.html:4 templates/authopenid/signin.html:21 +#: templates/authopenid/signin.html:5 templates/authopenid/signin.html:21 msgid "User login" -msgstr "Ingreso de usuario" +msgstr "" #: templates/authopenid/signin.html:28 #, python-format @@ -2557,10 +3078,6 @@ msgid "" "log in\n" " " msgstr "" -"\n" -" Tu respuesta a %(title)s %(summary)s será publicada una vez " -"que ingreses \n" -" " #: templates/authopenid/signin.html:35 #, python-format @@ -2569,127 +3086,85 @@ msgid "" " %(title)s %(summary)s will be posted once you log in\n" " " msgstr "" -"Tu pregunta \n" -" %(title)s %(summary)s será publicada una vez que ingreses\n" -" " -#: templates/authopenid/signin.html:40 +#: templates/authopenid/signin.html:42 msgid "Click to sign in through any of these services." -msgstr "Clickea para entrar por cualquiera de estos servicios." +msgstr "" -#: templates/authopenid/signin.html:103 +#: templates/authopenid/signin.html:117 msgid "Enter your <span id=\"enter_your_what\">Provider user name</span>" -msgstr "Ingresa tu <span id=\"enter_your_what\">nombre de usuario</span>" +msgstr "" -#: templates/authopenid/signin.html:110 +#: templates/authopenid/signin.html:124 msgid "" "Enter your <a class=\"openid_logo\" href=\"http://openid.net\">OpenID</a> " "web address" msgstr "" -"Ingresa tu dirección (URL) de <a class=\"openid_logo\" href=\"http://openid." -"net\">OpenID</a>" -#: templates/authopenid/signin.html:112 templates/authopenid/signin.html:137 +#: templates/authopenid/signin.html:126 templates/authopenid/signin.html:138 msgid "Login" -msgstr "Ingresar" +msgstr "" -#: templates/authopenid/signin.html:116 -msgid "we support two login modes" -msgstr "soportamos dos tipos de ingreso" +#: templates/authopenid/signin.html:129 +msgid "Enter your login name and password" +msgstr "" -#: templates/authopenid/signin.html:134 -msgid "Use login name and password" -msgstr "Nombre de usuario y contraseña" +#: templates/authopenid/signin.html:133 +msgid "Login name" +msgstr "" #: templates/authopenid/signin.html:135 -msgid "Login name" -msgstr "Nombre de usuario" +msgid "Password" +msgstr "" #: templates/authopenid/signin.html:139 -msgid "Create new account" -msgstr "Crear cuenta nueva" +msgid "Create account" +msgstr "" -#: templates/authopenid/signin.html:148 +#: templates/authopenid/signin.html:149 msgid "Why use OpenID?" -msgstr "¿Porqué usar OpenID?" +msgstr "" -#: templates/authopenid/signin.html:151 +#: templates/authopenid/signin.html:152 msgid "with openid it is easier" -msgstr "Con OpenID no necesitas crear un nuevo nombre de usuario y contraseña." +msgstr "" -#: templates/authopenid/signin.html:154 +#: templates/authopenid/signin.html:155 msgid "reuse openid" msgstr "" -"Puedes de forma segura re-usar el mismo nombre de usuario para todos los " -"sitios que acepten OpenID." -#: templates/authopenid/signin.html:157 +#: templates/authopenid/signin.html:158 msgid "openid is widely adopted" msgstr "" -"OpenID es extensamente usado. Hay más de 160,000,000 cuentas de OpenID en " -"uso en el mundo. Mas de 10,000 sitios aceptan OpenID." -#: templates/authopenid/signin.html:160 +#: templates/authopenid/signin.html:161 msgid "openid is supported open standard" msgstr "" -"OpenID es basado en un standard abierto, apoyado por muchas organizaciones." -#: templates/authopenid/signin.html:165 +#: templates/authopenid/signin.html:166 msgid "Find out more" -msgstr "Averigua más" +msgstr "" -#: templates/authopenid/signin.html:166 +#: templates/authopenid/signin.html:167 msgid "Get OpenID" -msgstr "Adquiere una OpenID" +msgstr "" -#: templates/authopenid/signup.html:4 templates/authopenid/signup.html.py:8 +#: templates/authopenid/signup.html:4 msgid "Signup" -msgstr "Registrate" +msgstr "" -#: templates/authopenid/signup.html:12 -msgid "" -"We support two types of user registration: conventional username/password, " -"and" +#: templates/authopenid/signup.html:8 +msgid "Create login name and password" msgstr "" -"Soportamos dos formas de registro de usuario: convencional usuario/" -"contraseña, y" -#: templates/authopenid/signup.html:12 -msgid "the OpenID method" -msgstr "OpenID" +#: templates/authopenid/signup.html:10 +msgid "Traditional signup info" +msgstr "" #: templates/authopenid/signup.html:17 -msgid "Sorry, looks like we have some errors" -msgstr "Ups, parece que hay errores." - -#: templates/authopenid/signup.html:35 -msgid "Conventional registration" -msgstr "Registro clásico" - -#: templates/authopenid/signup.html:36 -msgid "choose a user name" -msgstr "elije un nombre de usuario" - -#: templates/authopenid/signup.html:42 -msgid "back to login" -msgstr "volver al ingreso de usuario" - -#: templates/authopenid/signup.html:46 -msgid "Register with your OpenID" -msgstr "Registrate con tu OpenID" - -#: templates/authopenid/signup.html:49 -msgid "Login with your OpenID" -msgstr "Ingresar con tu OpenID" - -#~ msgid "site title" -#~ msgstr "Preguntalo.com.uy" - -#~ msgid "Have a total of" -#~ msgstr "Hay un total de" - -#~ msgid "/account/" -#~ msgstr "/cuenta/" +msgid "Create Account" +msgstr "" -#~ msgid "content/" -#~ msgstr "contenido/" +#: templates/authopenid/signup.html:19 +msgid "return to OpenID login" +msgstr "" diff --git a/log/README.TXT b/log/README.TXT new file mode 100755 index 00000000..9c51276d --- /dev/null +++ b/log/README.TXT @@ -0,0 +1 @@ +this file is just a placeholder so the empty directory is not ignored by version control diff --git a/settings.py b/settings.py index 50b3ab53..a0f6d80a 100755..100644 --- a/settings.py +++ b/settings.py @@ -5,12 +5,13 @@ import sys SITE_ID = 1 -ADMIN_MEDIA_PREFIX = '/forum/admin/media/' +ADMIN_MEDIA_PREFIX = '/admin_media/' SECRET_KEY = '$oo^&_m&qwbib=(_4m_n*zn-d=g#s0he5fx9xonnym#8p6yigm' # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.load_template_source', 'django.template.loaders.app_directories.load_template_source', + 'forum.modules.module_templates_loader', 'forum.skins.load_template_source', # 'django.template.loaders.eggs.load_template_source', ) @@ -27,14 +28,14 @@ MIDDLEWARE_CLASSES = ( 'forum.middleware.anon_user.ConnectToSessionMessagesMiddleware', 'forum.middleware.pagesize.QuestionsPageSizeMiddleware', 'forum.middleware.cancel.CancelActionMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware', - 'recaptcha_django.middleware.ReCaptchaMiddleware', + #'recaptcha_django.middleware.ReCaptchaMiddleware', 'django.middleware.transaction.TransactionMiddleware', + 'debug_toolbar.middleware.DebugToolbarMiddleware', ) TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.request', - 'context.application_settings', + 'forum.context.application_settings', #'django.core.context_processors.i18n', 'forum.user_messages.context_processors.user_messages',#must be before auth 'django.core.context_processors.auth', #this is required for admin @@ -67,9 +68,12 @@ INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.humanize', 'django.contrib.sitemaps', + 'debug_toolbar', + #'django_evolution', 'forum', 'django_authopenid', 'debug_toolbar' , + #'stackexchange', #se loader ) AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) diff --git a/settings_local.py.dist b/settings_local.py.dist index a7f61c26..bb7fd0dd 100755 --- a/settings_local.py.dist +++ b/settings_local.py.dist @@ -29,7 +29,14 @@ DATABASE_ENGINE = '' #mysql, etc DATABASE_HOST = '' DATABASE_PORT = '' -#Moved from settings.py for better organization. (please check it up to clean up settings.py) +#set this value to 'dummy://' if you don't want to use cache, or set up your favourite caching mechanism +#see http://docs.djangoproject.com/en/1.1/topics/cache/ for details +#example (set local file system cache in a cache folder in the root of the osqa install): +#CACHE_BACKEND = 'file://%s' % os.path.join(os.path.dirname(__file__),'cache').replace('\\','/') +CACHE_BACKEND = 'dummy://' + +#If you use memcache you may want to uncomment the following line to enable memcached based sessions +#SESSION_ENGINE = 'django.contrib.sessions.backends.cache_db' #email server settings SERVER_EMAIL = '' @@ -41,6 +48,9 @@ EMAIL_HOST='osqa.net' EMAIL_PORT='25' EMAIL_USE_TLS=False +#HACK - anonymous user email - for email-less users +ANONYMOUS_USER_EMAIL = 'anonymous@osqa.net' + #LOCALIZATIONS TIME_ZONE = 'America/New_York' @@ -71,33 +81,15 @@ EMAIL_UNIQUE = False APP_URL = 'http://osqa.net' #used by email notif system and RSS GOOGLE_SITEMAP_CODE = '' GOOGLE_ANALYTICS_KEY = '' -BOOKS_ON = False WIKI_ON = True -USE_EXTERNAL_LEGACY_LOGIN = False -EXTERNAL_LEGACY_LOGIN_HOST = 'login.osqa.net' -EXTERNAL_LEGACY_LOGIN_PORT = 80 -EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = '<span class="orange">OSQA</span>' FEEDBACK_SITE_URL = None #None or url EDITABLE_SCREEN_NAME = False #True or False - can user change screen name? DJANGO_VERSION = 1.1 RESOURCE_REVISION=4 -USE_SPHINX_SEARCH = False #if True all SPHINX_* settings are required -#also sphinx search engine and djangosphinxs app must be installed -#sample sphinx configuration file is /sphinx/sphinx.conf -SPHINX_API_VERSION = 0x113 #refer to djangosphinx documentation -SPHINX_SEARCH_INDICES=('osqa',) #a tuple of index names remember about a comma after the -#last item, especially if you have just one :) -SPHINX_SERVER='localhost' -SPHINX_PORT=3312 - #please get these at recaptcha.net RECAPTCHA_PRIVATE_KEY='...' RECAPTCHA_PUBLIC_KEY='...' OSQA_DEFAULT_SKIN = 'default' -#Facebook settings -USE_FB_CONNECT=False -FB_API_KEY='' #your api key from facebook -FB_SECRET='' #your application secret diff --git a/stackexchange/ANOMALIES b/stackexchange/ANOMALIES new file mode 100644 index 00000000..05a7dbdb --- /dev/null +++ b/stackexchange/ANOMALIES @@ -0,0 +1,14 @@ +* several user accounts with same email +* users with no openid +* users with no email (hack: gravatar set to settings.ANONYMOUS_USER_EMAIL) +* users with no screen name +* users with no email and no screen name (25% in homeschool) +* tag preferences are not stored explicitly (interesting/ignored) + maybe they are in se.User.preferences_raw + but the data there is not marked up and is kind of cryptic +* we don't have Community user. SE has one with id=-1 + this id may break the load script + potential break places are anywhere where is X.get_user() call + issues may happen with larger data sets where activity + of user "Community" is somehow reflected in a way + that load_stackexchange does not take care of diff --git a/stackexchange/README b/stackexchange/README new file mode 100644 index 00000000..64d8f5fb --- /dev/null +++ b/stackexchange/README @@ -0,0 +1,62 @@ +this app's function will be to: + +* install it's own tables (#todo: not yet automated) +* read SE xml dump into DjangoDB (automated) +* populate osqa database (automated) +* remove SE tables (#todo: not done yet) + +Current process to load SE data into OSQA is: +============================================== + +1) backup database + +2) unzip SE dump into dump_dir (any directory name) + you may want to make sure that your dump directory in .gitignore file + so that you don't publish it by mistake + +3) enable 'stackexchange' in the list of installed apps (probably aready in settings.py) + +4) (optional - create models.py for SE, which is included anyway) run: + + #a) run in-place removal of xml namspace prefix to make parsing easier + perl -pi -w -e 's/xs://g' $SE_DUMP_PATH/xsd/*.xsd + cd stackexchange + python parse_models.py $SE_DUMP_PATH/xsd/*.xsd > models.py + +5) Install stackexchange models (as well as any other missing models) + python manage.py syncdb + +6) make sure that osqa badges are installed + if not, run (example for mysql): + + mysql -u user -p dbname < sql_scripts/badges.sql + +7) load SE data: + + python manage.py load_stackexchange dump_dir + + if anything doesn't go right - run 'python manage.py flush' and repeat + steps 6 and 7 + +NOTES: +============ + +Here is the load script that I used for the testing +it assumes that SE dump has been unzipped inside the tmp directory + + #!/bin/sh$ + python manage.py flush + #delete all data + mysql -u osqa -p osqa < sql_scripts/badges.sql + python manage.py load_stackexchange tmp + +Untested parts are tagged with comments starting with +#todo: + +The test set did not have all the usage cases of StackExchange represented so +it may break with other sets. + +The job takes some time to run, especially +content revisions and votes - may be optimized + +Some of the fringe cases are described in file stackexchange/ANOMALIES diff --git a/stackexchange/__init__.py b/stackexchange/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/stackexchange/__init__.py diff --git a/stackexchange/management/__init__.py b/stackexchange/management/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/stackexchange/management/__init__.py diff --git a/stackexchange/management/commands/__init__.py b/stackexchange/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/stackexchange/management/commands/__init__.py diff --git a/stackexchange/management/commands/load_stackexchange.py b/stackexchange/management/commands/load_stackexchange.py new file mode 100644 index 00000000..afe4b9ea --- /dev/null +++ b/stackexchange/management/commands/load_stackexchange.py @@ -0,0 +1,804 @@ +from django.core.management.base import BaseCommand +from django.template.defaultfilters import slugify #todo: adopt unicode-aware slugify +#todo: http://stackoverflow.com/questions/837828/how-to-use-a-slug-in-django +import os +import re +import sys +import stackexchange.parse_models as se_parser +from xml.etree import ElementTree as et +from django.db import models +import forum.models as osqa +import stackexchange.models as se +from forum.forms import EditUserEmailFeedsForm +from forum.utils.html import sanitize_html +from django.conf import settings +from django.contrib.auth.models import Message as DjangoMessage +from django.utils.translation import ugettext as _ +#from markdown2 import Markdown +#markdowner = Markdown(html4tags=True) + +xml_read_order = ( + 'VoteTypes','UserTypes','Users','Users2Votes', + 'Badges','Users2Badges','CloseReasons','FlatPages', + 'MessageTypes','PostHistoryTypes','PostTypes','SchemaVersion', + 'Settings','SystemMessages','ThemeResources','ThemeTextResources', + 'ThrottleBucket','UserHistoryTypes','UserHistory', + 'Users2Badges','VoteTypes','Users2Votes','MessageTypes', + 'Posts','Posts2Votes','PostHistory','PostComments', + 'ModeratorMessages','Messages','Comments2Votes', + ) + +#association tables SE item id --> OSQA item id +#table associations are implied +#todo: there is an issue that these may be inconsistent with the database +USER = {}#SE User.id --> django(OSQA) User.id +QUESTION = {} +ANSWER = {} +NAMESAKE_COUNT = {}# number of times user name was used - for X.get_screen_name + +class X(object):# + """class with methods for handling some details + of SE --> OSQA mapping + """ + badge_type_map = {'1':'gold','2':'silver','3':'bronze'} + + osqa_supported_id_providers = ( + 'google','yahoo','aol','myopenid', + 'flickr','technorati', + 'wordpress','blogger','livejournal', + 'claimid','vidoop','verisign', + 'openidurl','facebook','local', + 'twitter' #oauth is not on this list, b/c it has no own url + ) + + #map SE VoteType -> osqa.User vote method + #created methods with the same call structure in osqa.User + #User.<vote_method>(post, timestamp=None, cancel=False) + vote_actions = { + 'UpMod':'upvote', + 'DownMod':'downvote', + 'AcceptedByOriginator':'accept_answer', + 'Offensive':'flag_post', + 'Favorite':'toggle_favorite_question', + } + + #these modes cannot be mixed + #only wiki is assumed to be mixable + exclusive_revision_modes = ( + 'initial','edit','rollback','lock', + 'migrate','close','merge','delete', + ) + + #badges whose names don't match exactly, but + #present in both SE and OSQA + badge_exceptions = {# SE --> OSQA + 'Citizen Patrol':'Citizen patrol',#single #todo: why sentence case? + 'Strunk & White':'Strunk & White',#single + 'Civic Duty':'Civic duty', + } + + revision_type_map = { + 'Initial Title':'initial', + 'Initial Body':'initial', + 'Initial Tags':'initial', + 'Edit Title':'edit', + 'Edit Body':'edit', + 'Edit Tags':'edit', + 'Rollback Title':'rollback', + 'Rollback Body':'rollback', + 'Rollback Tags':'rollback', + 'Post Closed':'close', + 'Post Reopened':'close', + 'Post Deleted':'delete', + 'Post Undeleted':'delete', + 'Post Locked':'lock', + 'Post Unlocked':'lock', + 'Community Owned':'wiki', + 'Post Migrated':'migrate', + 'Question Merged':'merge', + } + + close_reason_map = { + 1:1,#duplicate + 2:2,#off-topic + 3:3,#subjective and argumentative + 4:4,#not a real question + 5:7,#offensive + 6:6,#irrelevant or outdated question + 7:9,#too localized + 10:8,#spam + } + + @classmethod + def get_message_text(cls, se_m): + """try to intelligently translate + SE message to OSQA so that it makese sense in + our context + """ + #todo: properly translate messages + #todo: maybe work through more instances of messages + if se_m.message_type.name == 'Badge Notification': + return se_m.text + else: + if 'you are now an administrator' in se_m.text: + return _('Congratulations, you are now an Administrator') + elif re.search(r'^You have \d+ new',se_m.text): + bits = se_m.text.split('.') + text = bits[0] + if se_m.user.id == -1: + return None + url = cls.get_user(se_m.user).get_profile_url() + return '<a href="%s?sort=responses">%s</a>' % (url,text) + return None + + @classmethod + def get_post(cls, se_post): + #todo: fix this hack - either in-memory id association table + #or use database to store these associations + post_type = se_post.post_type.name + if post_type == 'Question': + return osqa.Question.objects.get(id=QUESTION[se_post.id].id) + elif post_type == 'Answer': + return osqa.Answer.objects.get(id=ANSWER[se_post.id].id) + else: + raise Exception('unknown post type %s' % post_type) + + @classmethod + def get_close_reason(cls, se_reason): + #todo: this is a guess - have not seen real data + se_reason = int(se_reason) + return cls.close_reason_map[se_reason] + + @classmethod + def get_user(cls, se_user): + #todo: same as get_post + return osqa.User.objects.get(id=USER[se_user.id].id) + + @classmethod + def get_post_revision_group_types(cls, rev_group): + rev_types = {} + for rev in rev_group: + rev_type = cls.get_post_revision_type(rev) + rev_types[rev_type] = 1 + rev_types = rev_types.keys() + + #make sure that exclusive rev modes are not mixed + exclusive = cls.exclusive_revision_modes + if len(rev_types) > 1 and all([t in exclusive for t in rev_types]): + tstr = ','.join(rev_types) + gstr = ','.join([str(rev.id) for rev in rev_group]) + msg = 'incompatible revision types %s in PostHistory %s' % (tstr,gstr) + raise Exception(msg) + return rev_types + + @classmethod + def clean_tags(cls, tags): + tags = re.subn(r'\s+',' ',tags.strip())[0] + bits = tags.split(' ') + tags = ' '.join([bit[1:-1] for bit in bits]) + tags = re.subn(r'\xf6','-',tags)[0] + return tags + + @classmethod + def get_screen_name(cls, name): + """always returns unique screen name + even if there are multiple users in SE + with the same exact screen name + """ + if name is None: + name = 'anonymous' + name = name.strip() + name = re.subn(r'\s+',' ',name)[0]#remove repeating spaces + + try: + u = osqa.User.objects.get(username = name) + try: + if u.location: + name += ', %s' % u.location + if name in NAMESAKE_COUNT: + NAMESAKE_COUNT[name] += 1 + name += ' %d' % NAMESAKE_COUNT[name] + else: + NAMESAKE_COUNT[name] = 1 + except osqa.User.DoesNotExist: + pass + except osqa.User.DoesNotExist: + NAMESAKE_COUNT[name] = 1 + return name + + @classmethod + def get_email(cls, email):#todo: fix fringe case - user did not give email! + if email is None: + return settings.ANONYMOUS_USER_EMAIL + else: + assert(email != '') + return email + + @classmethod + def get_post_revision_type(cls, rev): + rev_name = rev.post_history_type.name + rev_type = cls.revision_type_map.get(rev_name, None) + if rev_type is None: + raise Exception('dont understand revision type %s' % rev) + return rev_type + + #crude method of getting id provider name from the url + @classmethod + def get_openid_provider_name(cls, openid_url): + openid_str = str(openid_url) + bits = openid_str.split('/') + base_url = bits[2] #assume this is base url + url_bits = base_url.split('.') + provider_name = url_bits[-2].lower() + if provider_name not in cls.osqa_supported_id_providers: + raise Exception('could not determine login provider for %s' % openid_url) + return provider_name + + @staticmethod + def blankable(input): + if input is None: + return '' + else: + return input + + @classmethod + def parse_badge_summary(cls, badge_summary): + (gold,silver,bronze) = (0,0,0) + if badge_summary: + if len(badge_summary) > 3: + print 'warning: guessing that badge summary is comma separated' + print 'have %s' % badge_summary + bits = badge_summary.split(',') + else: + bits = [badge_summary] + for bit in bits: + m = re.search(r'^(?P<type>[1-3])=(?P<count>\d+)$', bit) + if not m: + raise Exception('could not parse badge summary: %s' % badge_summary) + else: + badge_type = cls.badge_type_map[m.groupdict()['type']] + locals()[badge_type] = int(m.groupdict()['count']) + return (gold,silver,bronze) + + @classmethod + def get_badge_name(cls, name): + return cls.badge_exceptions.get(name, name) + +class Command(BaseCommand): + help = 'Loads StackExchange data from unzipped directory of XML files into the OSQA database' + args = 'se_dump_dir' + + def handle(self, *arg, **kwarg): + if len(arg) < 1 or not os.path.isdir(arg[0]): + print 'Error: first argument must be a directory with all the SE *.xml files' + sys.exit(1) + + self.dump_path = arg[0] + #read the data into SE tables + for xml in xml_read_order: + xml_path = self.get_xml_path(xml) + table_name = self.get_table_name(xml) + self.load_xml_file(xml_path, table_name) + + #this is important so that when we clean up messages + #automatically generated by the procedures below + #we do not delete old messages + #todo: unfortunately this may need to be redone + #when we upgrade to django 1.2 and definitely by 1.4 when + #the current message system will be replaced with the + #django messages framework + self.save_osqa_message_id_list() + + #transfer data into OSQA tables + print 'Transferring users...', + sys.stdout.flush() + self.transfer_users() + print 'done.' + print 'Transferring content edits...', + sys.stdout.flush() + self.transfer_question_and_answer_activity() + print 'done.' + print 'Transferring view counts...', + sys.stdout.flush() + self.transfer_question_view_counts() + print 'done.' + print 'Transferring comments...', + sys.stdout.flush() + self.transfer_comments() + print 'done.' + print 'Transferring badges and badge awards...', + sys.stdout.flush() + self.transfer_badges() + print 'done.' + print 'Transferring votes...', + sys.stdout.flush() + self.transfer_votes()#includes favorites, accepts and flags + print 'done.' + + self.cleanup_messages()#delete autogenerated messages + self.transfer_messages() + + #todo: these are not clear how to go about + self.transfer_update_subscriptions() + self.transfer_tag_preferences() + self.transfer_meta_pages() + + def save_osqa_message_id_list(self): + id_list = list(DjangoMessage.objects.all().values('id')) + self._osqa_message_id_list = id_list + + def cleanup_messages(self): + """deletes messages generated by the load process + """ + id_list = self._osqa_message_id_list + mset = DjangoMessage.objects.all().exclude(id__in=id_list) + mset.delete() + + def transfer_messages(self): + """transfers some messages from + SE to OSQA + """ + for m in se.Message.objects.all(): + if m.is_read: + continue + if m.user.id == -1: + continue + u = X.get_user(m.user) + text = X.get_message_text(m) + if text: + u.message_set.create( + message=text, + ) + + def _process_post_initial_revision_group(self, rev_group): + + title = None + text = None + tags = None + wiki = False + author = USER[rev_group[0].user.id] + added_at = rev_group[0].creation_date + + for rev in rev_group: + rev_type = rev.post_history_type.name + if rev_type == 'Initial Title': + title = rev.text + elif rev_type == 'Initial Body': + text = rev.text + elif rev_type == 'Initial Tags': + tags = X.clean_tags(rev.text) + elif rev_type == 'Community Owned': + wiki = True + else: + raise Exception('unexpected revision type %s' % rev_type) + + post_type = rev_group[0].post.post_type.name + if post_type == 'Question': + q = osqa.Question.objects.create_new( + title = title, + author = author, + added_at = added_at, + wiki = wiki, + tagnames = tags, + text = text + ) + QUESTION[rev_group[0].post.id] = q + elif post_type == 'Answer': + q = X.get_post(rev_group[0].post.parent) + a = osqa.Answer.objects.create_new( + question = q, + author = author, + added_at = added_at, + wiki = wiki, + text = text, + ) + ANSWER[rev_group[0].post.id] = a + else: + post_id = rev_group[0].post.id + raise Exception('unknown post type %s for id=%d' % (post_type, post_id)) + + def _process_post_edit_revision_group(self, rev_group): + #question apply edit + (title, text, tags) = (None, None, None) + for rev in rev_group: + rev_type = rev.post_history_type.name + if rev_type == 'Edit Title': + title = rev.text + elif rev_type == 'Edit Body': + text = rev.text + elif rev_type == 'Edit Tags': + tags = X.clean_tags(rev.text) + elif rev_type == 'Community Owned': + pass + else: + raise Exception('unexpected revision type %s' % rev_type) + + rev0 = rev_group[0] + edited_by = USER[rev0.user.id] + edited_at = rev0.creation_date + comment = ';'.join([rev.comment for rev in rev_group if rev.comment]) + post_type = rev0.post.post_type.name + + if post_type == 'Question': + q = X.get_post(rev0.post) + q.apply_edit( + edited_at = edited_at, + edited_by = edited_by, + title = title, + text = text, + comment = comment, + tags = tags, + ) + elif post_type == 'Answer': + a = X.get_post(rev0.post) + a.apply_edit( + edited_at = edited_at, + edited_by = edited_by, + text = text, + comment = comment, + ) + + def _make_post_wiki(self, rev_group): + #todo: untested + for rev in rev_group: + if rev.post_history_type.name == 'Community Owned': + p = X.get_post(rev.post) + u = X.get_user(rev.user) + t = rev.creation_date + p.wiki = True + p.wikified_at = t + p.wikified_by = u + self.mark_activity(p,u,t) + p.save() + return + + def mark_activity(self,p,u,t): + """p,u,t - post, user, timestamp + """ + if isinstance(p, osqa.Question): + p.last_activity_by = u + p.last_activity_at = t + elif isinstance(p, osqa.Answer): + p.question.last_activity_by = u + p.question.last_activity_at = t + p.question.save() + + def _process_post_rollback_revision_group(self, rev_group): + #todo: don't know what to do here as there were no + #such data available + pass + + def _process_post_lock_revision_group(self, rev_group): + #todo: untested + for rev in rev_group: + rev_type = rev.post_history_type.name + if rev_type.endswith('ocked'): + t = rev.creation_date + u = X.get_user(rev.user) + p = X.get_post(rev.post) + if rev_type == 'Post Locked': + p.locked = True + p.locked_by = u + p.locked_at = t + elif rev_type == 'Post Unlocked': + p.locked = False + p.locked_by = None + p.locked_at = None + else: + return + self.mark_activity(p,u,t) + p.save() + return + + def _process_post_close_revision_group(self, rev_group): + #todo: untested + for rev in rev_group: + if rev.post.post_type.name != 'Question': + return + rev_type = rev.post_history_type.name + if rev_type in ('Post Closed', 'Post Reopened'): + t = rev.creation_date + u = X.get_user(rev.user) + p = X.get_post(rev.post) + if rev_type == 'Post Closed': + p.closed = True + p.closed_at = t + p.closed_by = u + p.close_reason = X.get_close_reason(rev.text) + elif rev_type == 'Post Reopened': + p.closed = False + p.closed_at = None + p.closed_by = None + p.close_reason = None + self.mark_activity(p,u,t) + p.save() + return + + def _process_post_delete_revision_group(self, rev_group): + #todo: untested + for rev in rev_group: + rev_type = rev.post_history_type.name + if rev_type.endswith('eleted'): + t = rev.creation_date + u = X.get_user(rev.user) + p = X.get_post(rev.post) + if rev_type == 'Post Deleted': + p.deleted = True + p.deleted_at = t + p.deleted_by = u + elif rev_type == 'Post Undeleted': + p.deleted = False + p.deleted_at = None + p.deleted_by = None + self.mark_activity(p,u,t) + p.save() + return + + def _process_post_revision_group(self, rev_group): + #determine revision type + #'initial','edit','rollback','lock', + #'migrate','close','merge','delete', + rev_types = X.get_post_revision_group_types(rev_group) + if 'initial' in rev_types: + self._process_post_initial_revision_group(rev_group) + elif 'edit' in rev_types: + self._process_post_edit_revision_group(rev_group) + elif 'rollback' in rev_types: + self._process_post_rollback_revision_group(rev_group) + elif 'lock' in rev_types: + self._process_post_lock_revision_group(rev_group) + elif 'close' in rev_types: + self._process_post_close_revision_group(rev_group) + elif 'delete' in rev_types: + self._process_post_delete_revision_group(rev_group) + else: + pass + #todo: rollback, lock, close and delete are + #not tested + #merge and migrate actions are ignored + #wiki is mixable with other groups, so process it in addition + if 'wiki' in rev_types: + self._make_post_wiki(rev_group) + + def transfer_tag_preferences(self): + #todo: figure out where these are stored in SE + #maybe in se.User.preferences_raw? + pass + + def transfer_question_and_answer_activity(self): + """transfers all question and answer + edits and related status changes + """ + #assuming that there are only two post types + se_revs = se.PostHistory.objects.all() + #assuming that chronologial order is correct and there + #will be no problems of data integrity upon insertion of records + se_revs = se_revs.order_by('creation_date','revision_guid') + #todo: ignored fringe case - no revisions + c_guid = se_revs[0].revision_guid + c_group = [] + #this loop groups revisions by revision id, then calls process function + #for the revision grup (elementary revisions posted at once) + for se_rev in se_revs: + if se_rev.revision_guid == c_guid: + c_group.append(se_rev) + else: + self._process_post_revision_group(c_group) + c_group = [] + c_group.append(se_rev) + c_guid = se_rev.revision_guid + if len(c_group) != 0: + self._process_post_revision_group(c_group) + + def transfer_comments(self): + for se_c in se.PostComment.objects.all(): + if se_c.deletion_date: + print 'Warning deleted comment %d dropped' % se_c.id + continue + se_post = se_c.post + if se_post.post_type.name == 'Question': + osqa_post = QUESTION[se_post.id] + elif se_post.post_type.name == 'Answer': + osqa_post = ANSWER[se_post.id] + + osqa_post.add_comment( + comment = se_c.text, + added_at = se_c.creation_date, + user = USER[se_c.user.id] + ) + + def _install_missing_badges(self): + self._missing_badges = {} + for se_b in se.Badge.objects.all(): + name = X.get_badge_name(se_b.name) + try: + osqa.Badge.objects.get(name=name) + except: + self._missing_badges[name] = 0 + if len(se_b.description) > 300: + print 'Warning truncated description for badge %d' % se_b.id + osqa.Badge.objects.create( + name = name, + slug = slugify(name), + description = se_b.description, + multiple = (not se_b.single), + type = se_b.class_type + ) + + def _award_badges(self): + #note: SE does not keep information on + #content-related badges like osqa does + for se_a in se.User2Badge.objects.all(): + if se_a.user.id == -1: + continue #skip community user + u = USER[se_a.user.id] + badge_name = X.get_badge_name(se_a.badge.name) + b = osqa.Badge.objects.get(name=badge_name) + if b.multiple == False: + if b.award_badge.count() > 0: + continue + #todo: fake content object here b/c SE does not support this + #todo: but osqa requires related content object + osqa.Award.objects.create( + user=u, + badge=b, + awarded_at=se_a.date, + content_object=u, + ) + if b.name in self._missing_badges: + self._missing_badges[b.name] += 1 + + def _cleanup_badges(self): + d = self._missing_badges + unused = [name for name in d.keys() if d[name] == 0] + osqa.Badge.objects.filter(name__in=unused).delete() + installed = [name for name in d.keys() if d[name] > 0] + print 'Warning - following unsupported badges were installed:' + print ', '.join(installed) + + def transfer_badges(self): + #note: badge level is neglected + #1) install missing badges + self._install_missing_badges() + #2) award badges + self._award_badges() + #3) delete unused newly installed badges + self._cleanup_badges() + pass + + def transfer_question_view_counts(self): + for se_q in se.Post.objects.filter(post_type__name='Question'): + q = X.get_post(se_q) + q.view_count = se_q.view_count + q.save() + + + def transfer_votes(self): + for v in se.Post2Vote.objects.all(): + vote_type = v.vote_type.name + if not vote_type in X.vote_actions: + continue + + u = X.get_user(v.user) + p = X.get_post(v.post) + m = X.vote_actions[vote_type] + vote_method = getattr(osqa.User, m) + vote_method(u, p, timestamp = v.creation_date) + if v.deletion_date: + vote_method(u, p, timestamp = v.deletion_date, cancel=True) + + def transfer_update_subscriptions(self): + #todo: not clear where this is stored in SE + #maybe in se.User.preferences_raw? + pass + + def transfer_meta_pages(self): + #here we actually don't have anything in the database yet + #so we can't do this + pass + + def load_xml_file(self, xml_path, table_name): + tree = et.parse(xml_path) + print 'loading from %s to %s' % (xml_path, table_name) , + model = models.get_model('stackexchange', table_name) + i = 0 + for row in tree.findall('.//row'): + model_entry = model() + i += 1 + for col in row.getchildren(): + field_name = se_parser.parse_field_name(col.tag) + field_type = model._meta.get_field(field_name) + field_value = se_parser.parse_value(col.text, field_type) + setattr(model_entry, field_name, field_value) + model_entry.save() + print '... %d objects saved' % i + + def get_table_name(self,xml): + return se_parser.get_table_name(xml) + + def get_xml_path(self, xml): + xml_path = os.path.join(self.dump_path, xml + '.xml') + if not os.path.isfile(xml_path): + print 'Error: file %s not found' % xml_path + sys.exit(1) + return xml_path + + def transfer_users(self): + for se_u in se.User.objects.all(): + if se_u.id < 1:#skip the Community user + continue + u = osqa.User() + u_type = se_u.user_type.name + if u_type == 'Administrator': + u.is_superuser = True + elif u_type == 'Moderator': + u.is_staff = True + elif u_type not in ('Unregistered', 'Registered'): + raise Exception('unknown user type %s' % u_type) + + #if user is not registered, no association record created + #we do not allow posting by users who are not authenticated + #probably they'll just have to "recover" their account by email + if u_type != 'Unregistered': + assert(se_u.open_id)#everybody must have open_id + u_auth = osqa.AuthKeyUserAssociation() + u_auth.key = se_u.open_id + u_auth.provider = X.get_openid_provider_name(se_u.open_id) + u_auth.added_at = se_u.creation_date + + if se_u.open_id is None and se_u.email is None: + print 'Warning: SE user %d is not recoverable (no email or openid)' + + u.reputation = 1#se_u.reputation, it's actually re-computed + u.last_seen = se_u.last_access_date + u.email = X.get_email(se_u.email) + u.location = X.blankable(se_u.location) + u.date_of_birth = se_u.birthday #dattime -> date + u.website = X.blankable(se_u.website_url) + u.about = X.blankable(se_u.about_me) + u.last_login = se_u.last_login_date + u.date_joined = se_u.creation_date + u.is_active = True #todo: this may not be the case + + u.username = X.get_screen_name(se_u.display_name) + u.real_name = X.blankable(se_u.real_name) + + (gold,silver,bronze) = X.parse_badge_summary(se_u.badge_summary) + u.gold = gold + u.silver = silver + u.bronze = bronze + + #todo: we don't have these fields + #views - number of profile views? + #has_replies + #has_message + #opt_in_recruit + #last_login_ip + #open_id_alt - ?? + #preferences_raw - not clear how to use + #display_name_cleaned - lowercased, srtipped name + #timed_penalty_date + #phone + + #don't know how to handle these - there was no usage example + #password_id + #guid + + #ignored + #last_email_date - this translates directly to EmailFeedSetting.reported_at + + #save the data + u.save() + form = EditUserEmailFeedsForm() + form.reset() + if se_u.opt_in_email == True:#set up daily subscription on "own" items + form.initial['individually_selected'] = 'd' + form.initial['asked_by_me'] = 'd' + form.initial['answered_by_me'] = 'd' + # + form.save(user=u, save_unbound=True) + + if 'u_auth' in locals(): + u_auth.user = u + u_auth.save() + USER[se_u.id] = u diff --git a/stackexchange/models.py b/stackexchange/models.py new file mode 100644 index 00000000..a30a9859 --- /dev/null +++ b/stackexchange/models.py @@ -0,0 +1,266 @@ +from django.db import models +class Badge(models.Model): + id = models.IntegerField(primary_key=True) + class_type = models.IntegerField(null=True) + name = models.CharField(max_length=50, null=True) + description = models.TextField(null=True) + single = models.NullBooleanField(null=True) + secret = models.NullBooleanField(null=True) + tag_based = models.NullBooleanField(null=True) + command = models.TextField(null=True) + award_frequency = models.IntegerField(null=True) + +class CloseReason(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=200, null=True) + description = models.CharField(max_length=256, null=True) + display_order = models.IntegerField(null=True) + +class Comment2Vote(models.Model): + id = models.IntegerField(primary_key=True) + post_comment = models.ForeignKey('PostComment', related_name='Comment2Vote_by_post_comment_set', null=True) + vote_type = models.ForeignKey('VoteType', related_name='Comment2Vote_by_vote_type_set', null=True) + creation_date = models.DateTimeField(null=True) + user = models.ForeignKey('User', related_name='Comment2Vote_by_user_set', null=True) + ip_address = models.CharField(max_length=40, null=True) + user_display_name = models.CharField(max_length=40, null=True) + deletion_date = models.DateTimeField(null=True) + +class FlatPage(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + url = models.CharField(max_length=128, null=True) + value = models.TextField(null=True) + content_type = models.CharField(max_length=50, null=True) + active = models.NullBooleanField(null=True) + use_master = models.NullBooleanField(null=True) + +class Message(models.Model): + id = models.IntegerField(primary_key=True) + user = models.ForeignKey('User', related_name='Message_by_user_set', null=True) + message_type = models.ForeignKey('MessageType', related_name='Message_by_message_type_set', null=True) + is_read = models.NullBooleanField(null=True) + creation_date = models.DateTimeField(null=True) + text = models.TextField(null=True) + post = models.ForeignKey('Post', related_name='Message_by_post_set', null=True) + +class MessageType(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + description = models.CharField(max_length=300, null=True) + +class ModeratorMessage(models.Model): + id = models.IntegerField(primary_key=True) + message_type = models.ForeignKey('MessageType', related_name='ModeratorMessage_by_message_type_set', null=True) + creation_date = models.DateTimeField(null=True) + creation_ip_address = models.CharField(max_length=40, null=True) + text = models.TextField(null=True) + user = models.ForeignKey('User', related_name='ModeratorMessage_by_user_set', null=True) + post = models.ForeignKey('Post', related_name='ModeratorMessage_by_post_set', null=True) + deletion_date = models.DateTimeField(null=True) + deletion_user = models.ForeignKey('User', related_name='ModeratorMessage_by_deletion_user_set', null=True) + deletion_ip_address = models.CharField(max_length=40, null=True) + user_display_name = models.CharField(max_length=40, null=True) + +class PostComment(models.Model): + id = models.IntegerField(primary_key=True) + post = models.ForeignKey('Post', related_name='PostComment_by_post_set', null=True) + text = models.TextField(null=True) + creation_date = models.DateTimeField(null=True) + ip_address = models.CharField(max_length=15, null=True) + user = models.ForeignKey('User', related_name='PostComment_by_user_set', null=True) + user_display_name = models.CharField(max_length=30, null=True) + deletion_date = models.DateTimeField(null=True) + deletion_user = models.ForeignKey('User', related_name='PostComment_by_deletion_user_set', null=True) + score = models.IntegerField(null=True) + +class PostHistoryType(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + description = models.CharField(max_length=300, null=True) + +class PostHistory(models.Model): + id = models.IntegerField(primary_key=True) + post_history_type = models.ForeignKey('PostHistoryType', related_name='PostHistory_by_post_history_type_set', null=True) + post = models.ForeignKey('Post', related_name='PostHistory_by_post_set', null=True) + revision_guid = models.CharField(max_length=64, null=True) + creation_date = models.DateTimeField(null=True) + ip_address = models.CharField(max_length=40, null=True) + user = models.ForeignKey('User', related_name='PostHistory_by_user_set', null=True) + comment = models.CharField(max_length=400, null=True) + text = models.TextField(null=True) + user_display_name = models.CharField(max_length=40, null=True) + user_email = models.CharField(max_length=100, null=True) + user_website_url = models.CharField(max_length=200, null=True) + +class Post2Vote(models.Model): + id = models.IntegerField(primary_key=True) + post = models.ForeignKey('Post', related_name='Post2Vote_by_post_set', null=True) + user = models.ForeignKey('User', related_name='Post2Vote_by_user_set', null=True) + vote_type = models.ForeignKey('VoteType', related_name='Post2Vote_by_vote_type_set', null=True) + creation_date = models.DateTimeField(null=True) + deletion_date = models.DateTimeField(null=True) + target_user = models.ForeignKey('User', related_name='Post2Vote_by_target_user_set', null=True) + target_rep_change = models.IntegerField(null=True) + voter_rep_change = models.IntegerField(null=True) + comment = models.CharField(max_length=150, null=True) + ip_address = models.CharField(max_length=40, null=True) + linked_post = models.ForeignKey('Post', related_name='Post2Vote_by_linked_post_set', null=True) + +class Post(models.Model): + id = models.IntegerField(primary_key=True) + post_type = models.ForeignKey('PostType', related_name='Post_by_post_type_set', null=True) + creation_date = models.DateTimeField(null=True) + score = models.IntegerField(null=True) + view_count = models.IntegerField(null=True) + body = models.TextField(null=True) + owner_user = models.ForeignKey('User', related_name='Post_by_owner_user_set', null=True) + last_editor_user = models.ForeignKey('User', related_name='Post_by_last_editor_user_set', null=True) + last_edit_date = models.DateTimeField(null=True) + last_activity_date = models.DateTimeField(null=True) + last_activity_user = models.ForeignKey('User', related_name='Post_by_last_activity_user_set', null=True) + parent = models.ForeignKey('self', related_name='Post_by_parent_set', null=True) + accepted_answer = models.ForeignKey('self', related_name='Post_by_accepted_answer_set', null=True) + title = models.CharField(max_length=250, null=True) + tags = models.CharField(max_length=150, null=True) + community_owned_date = models.DateTimeField(null=True) + history_summary = models.CharField(max_length=150, null=True) + answer_score = models.IntegerField(null=True) + answer_count = models.IntegerField(null=True) + comment_count = models.IntegerField(null=True) + favorite_count = models.IntegerField(null=True) + deletion_date = models.DateTimeField(null=True) + closed_date = models.DateTimeField(null=True) + locked_date = models.DateTimeField(null=True) + locked_duration = models.IntegerField(null=True) + owner_display_name = models.CharField(max_length=40, null=True) + last_editor_display_name = models.CharField(max_length=40, null=True) + bounty_amount = models.IntegerField(null=True) + bounty_closes = models.DateTimeField(null=True) + bounty_closed = models.DateTimeField(null=True) + last_owner_email_date = models.DateTimeField(null=True) + +class PostType(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + description = models.CharField(max_length=300, null=True) + +class SchemaVersion(models.Model): + version = models.IntegerField(null=True) + +class Setting(models.Model): + id = models.IntegerField(primary_key=True) + key = models.CharField(max_length=256, null=True) + value = models.TextField(null=True) + +class SystemMessage(models.Model): + id = models.IntegerField(primary_key=True) + user = models.ForeignKey('User', related_name='SystemMessage_by_user_set', null=True) + creation_date = models.DateTimeField(null=True) + text = models.TextField(null=True) + deletion_date = models.DateTimeField(null=True) + deletion_user = models.ForeignKey('User', related_name='SystemMessage_by_deletion_user_set', null=True) + +class Tag(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + count = models.IntegerField(null=True) + user = models.ForeignKey('User', related_name='Tag_by_user_set', null=True) + creation_date = models.DateTimeField(null=True) + is_moderator_only = models.NullBooleanField(null=True) + is_required = models.NullBooleanField(null=True) + aliases = models.CharField(max_length=200, null=True) + +class ThemeResource(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + value = models.TextField(null=True) + content_type = models.CharField(max_length=50, null=True) + version = models.CharField(max_length=6, null=True) + +class ThemeTextResource(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + value = models.TextField(null=True) + content_type = models.CharField(max_length=50, null=True) + +class ThrottleBucket(models.Model): + id = models.IntegerField(primary_key=True) + type = models.CharField(max_length=256, null=True) + ip_address = models.CharField(max_length=64, null=True) + tokens = models.IntegerField(null=True) + last_update = models.DateTimeField(null=True) + +class UserHistoryType(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + description = models.CharField(max_length=300, null=True) + +class UserHistory(models.Model): + id = models.IntegerField(primary_key=True) + user_history_type = models.ForeignKey('UserHistoryType', related_name='UserHistory_by_user_history_type_set', null=True) + creation_date = models.DateTimeField(null=True) + ip_address = models.CharField(max_length=40, null=True) + user = models.ForeignKey('User', related_name='UserHistory_by_user_set', null=True) + comment = models.CharField(max_length=400, null=True) + user_display_name = models.CharField(max_length=40, null=True) + moderator_user = models.ForeignKey('User', related_name='UserHistory_by_moderator_user_set', null=True) + reputation = models.IntegerField(null=True) + +class User2Badge(models.Model): + id = models.IntegerField(primary_key=True) + user = models.ForeignKey('User', related_name='User2Badge_by_user_set', null=True) + badge = models.ForeignKey('Badge', related_name='User2Badge_by_badge_set', null=True) + date = models.DateTimeField(null=True) + comment = models.CharField(max_length=50, null=True) + +class User2Vote(models.Model): + id = models.IntegerField(primary_key=True) + user = models.ForeignKey('User', related_name='User2Vote_by_user_set', null=True) + vote_type = models.ForeignKey('VoteType', related_name='User2Vote_by_vote_type_set', null=True) + target_user = models.ForeignKey('User', related_name='User2Vote_by_target_user_set', null=True) + creation_date = models.DateTimeField(null=True) + deletion_date = models.DateTimeField(null=True) + ip_address = models.CharField(max_length=40, null=True) + +class User(models.Model): + id = models.IntegerField(primary_key=True) + user_type = models.ForeignKey('UserType', related_name='User_by_user_type_set', null=True) + open_id = models.CharField(max_length=200, null=True) + reputation = models.IntegerField(null=True) + views = models.IntegerField(null=True) + creation_date = models.DateTimeField(null=True) + last_access_date = models.DateTimeField(null=True) + has_replies = models.NullBooleanField(null=True) + has_message = models.NullBooleanField(null=True) + opt_in_email = models.NullBooleanField(null=True) + opt_in_recruit = models.NullBooleanField(null=True) + last_login_date = models.DateTimeField(null=True) + last_email_date = models.DateTimeField(null=True) + last_login_ip = models.CharField(max_length=15, null=True) + open_id_alt = models.CharField(max_length=200, null=True) + email = models.CharField(max_length=100, null=True) + display_name = models.CharField(max_length=40, null=True) + display_name_cleaned = models.CharField(max_length=40, null=True) + website_url = models.CharField(max_length=200, null=True) + real_name = models.CharField(max_length=100, null=True) + location = models.CharField(max_length=100, null=True) + birthday = models.DateTimeField(null=True) + badge_summary = models.CharField(max_length=50, null=True) + about_me = models.TextField(null=True) + preferences_raw = models.TextField(null=True) + timed_penalty_date = models.DateTimeField(null=True) + guid = models.CharField(max_length=64, null=True) + phone = models.CharField(max_length=20, null=True) + password_id = models.IntegerField(null=True) + +class UserType(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + description = models.CharField(max_length=300, null=True) + +class VoteType(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=50, null=True) + description = models.CharField(max_length=300, null=True) + diff --git a/stackexchange/parse_models.py b/stackexchange/parse_models.py new file mode 100644 index 00000000..64796e57 --- /dev/null +++ b/stackexchange/parse_models.py @@ -0,0 +1,225 @@ +from xml.etree import ElementTree as et +import sys +import re +import os +if __name__ != '__main__':#hack do not import models if run as script + from django.db import models +from datetime import datetime + +table_prefix = ''#StackExchange or something, if needed +date_time_format = '%Y-%m-%dT%H:%M:%S' #note that fractional part of second is lost +time_re = re.compile(r'(\.[\d]+)?$') +loader_app_name = os.path.dirname(__file__) + +types = { + 'unsignedByte':'models.IntegerField', + 'FK':'models.ForeignKey', + 'PK':'models.IntegerField', + 'string':'models.CharField', + 'text':'models.TextField', + 'int':'models.IntegerField', + 'boolean':'models.NullBooleanField', + 'dateTime':'models.DateTimeField', + 'base64Binary':'models.TextField', + 'double':'models.IntegerField', +} + +def camel_to_python(camel): + """http://stackoverflow.com/questions/1175208/ + """ + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + +def singular(word): + if word.endswith('s'): + return word[:-1] + else: + return word + +def get_table_name(name): + """Determine db table name + from the basename of the .xml file + """ + out = table_prefix + if name.find('2') == -1: + out += singular(name) + else: + bits = name.split('2') + bits = map(singular, bits) + out += '2'.join(bits) + return out + +class DjangoModel(object): + def __init__(self, name): + self.name = get_table_name(name) + self.fields = [] + def add_field(self,field): + field.table = self + self.fields.append(field) + def __str__(self): + out = 'class %s(models.Model):\n' % self.name + for f in self.fields: + out += ' %s\n' % str(f) + return out + +class DjangoField(object): + def __init__(self, name, type, restriction = None): + self.name = camel_to_python(name) + if self.name == 'class': + self.name = 'class_type'#work around python keyword + self.type = type + self.table = None + self.restriction = restriction + self.relation = None + + def __str__(self): + out = '%s = %s(' % (self.name, types[self.type]) + if self.type == 'FK': + out += "'%s'" % self.relation + out += ", related_name='%s_by_%s_set'" % (self.table.name, self.name) + out += ', null=True'#nullable to make life easier + elif self.type == 'PK': + out += 'primary_key=True' + elif self.restriction != -1: + if self.type == 'string': + out += 'max_length=%s' % self.restriction + out += ', null=True' + else: + raise Exception('restriction (max_length) supported only for string type') + else: + out += 'null=True' + out += ')' + return out + + def get_type(self): + return self.type + +class DjangoPK(DjangoField): + def __init__(self): + self.name = 'id' + self.type = 'PK' + +class DjangoFK(DjangoField): + def __init__(self, source_name): + bits = source_name.split('Id') + if len(bits) == 2 and bits[1] == '': + name = bits[0] + super(DjangoFK, self).__init__(name, 'FK') + self.set_relation(name) + + def set_relation(self, name): + """some relations need to be mapped + to actual tables + """ + self.relation = table_prefix + if name.endswith('User'): + self.relation += 'User' + elif name.endswith('Post'): + self.relation += 'Post' + elif name in ('AcceptedAnswer','Parent'): + self.relation = 'self' #self-referential Post model + else: + self.relation += name + def get_relation(self): + return self.relation + +def get_col_type(col): + type = col.get('type') + restriction = -1 + if type == None: + type_e = col.find('.//simpleType/restriction') + type = type_e.get('base') + try: + restriction = int(type_e.getchildren()[0].get('value')) + except: + restriction = -1 + if restriction > 400: + type = 'text' + restriction = -1 + return type, restriction + +def make_field_from_xml_tree(xml_element): + """used by the model parser + here we need to be detailed about field types + because this defines the database schema + """ + name = xml_element.get('name') + if name == 'LinkedVoteId':#not used + return None + if name == 'Id': + field = DjangoPK() + elif name.endswith('Id') and name not in ('OpenId','PasswordId'): + field = DjangoFK(name) + elif name.endswith('GUID'): + field = DjangoField(name, 'string', 64) + else: + type, restriction = get_col_type(xml_element) + field = DjangoField(name, type, restriction) + return field + +def parse_field_name(input): + """used by the data reader + + The problem is that I've scattered + code for determination of field name over three classes: + DjangoField, DjangoPK and DjangoFK + so the function actually cretes fake field objects + many time over + """ + if input == 'Id': + return DjangoPK().name + elif input in ('OpenId', 'PasswordId'): + return DjangoField(input, 'string', 7).name#happy fake field + elif input.endswith('Id'): + return DjangoFK(input).name#real FK field + else: + return DjangoField(input, 'string', 7).name#happy fake field + +def parse_value(input, field_object): + if isinstance(field_object, models.ForeignKey): + try: + id = int(input) + except: + raise Exception('non-numeric foreign key %s' % input) + related_model = field_object.rel.to + try: + return related_model.objects.get(id=id) + except related_model.DoesNotExist: + obj = related_model(id=id) + obj.save()#save fake empty object + return obj + elif isinstance(field_object, models.IntegerField): + try: + return int(input) + except: + raise Exception('expected integer, found %s' % input) + elif isinstance(field_object, models.CharField): + return input + elif isinstance(field_object, models.TextField): + return input + elif isinstance(field_object, models.BooleanField): + try: + return bool(input) + except: + raise Exception('boolean value expected %s found' % input) + elif isinstance(field_object, models.DateTimeField): + input = time_re.sub('', input) + try: + return datetime.strptime(input, date_time_format) + except: + raise Exception('datetime expected "%s" found' % input) + +print 'from django.db import models' +for file in sys.argv: + if '.xsd' in file: + tname = os.path.basename(file).replace('.xsd','') + tree = et.parse(file) + + model = DjangoModel(tname) + + row = tree.find('.//sequence') + for col in row.getchildren(): + field = make_field_from_xml_tree(col) + if field: + model.add_field(field) + print model |