Date: Thu, 20 Apr 2017 02:26:41 -0400
Subject: [PATCH 38/78] Update forms.py
---
zds/tutorialv2/forms.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index b71a88d02d..d48e307214 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -263,7 +263,7 @@ class ContentForm(ContainerForm):
widget=forms.CheckboxSelectMultiple()
)
- def _create_layout(self, hide_help):
+ def _create_layout(self, hide_help, **kwargs):
html_part = HTML(_(u"Demander de l'aide à la communauté !
"
u"Si vous avez besoin d'un coup de main, "
u"sélectionnez une ou plusieurs catégories d'aide ci-dessous "
@@ -349,7 +349,7 @@ def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_class = 'content-wrapper'
self.helper.form_method = 'post'
- self._create_layout(for_tribune)
+ self._create_layout(for_tribune, kwargs)
if 'type' in self.initial:
self.helper['type'].wrap(
From e5fe212b2d1ae4abcf11b87ec83d0cceda825b6d Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Thu, 20 Apr 2017 02:45:22 -0400
Subject: [PATCH 39/78] Update forms.py
---
zds/tutorialv2/forms.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index d48e307214..7afb0760a1 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -349,7 +349,7 @@ def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_class = 'content-wrapper'
self.helper.form_method = 'post'
- self._create_layout(for_tribune, kwargs)
+ self._create_layout(for_tribune, **kwargs)
if 'type' in self.initial:
self.helper['type'].wrap(
From 42d52aa4a33e9922aa8d3414d3c3c63888694dda Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Sat, 22 Apr 2017 14:41:57 +0200
Subject: [PATCH 40/78] Update forms.py
---
zds/tutorialv2/forms.py | 7 -------
1 file changed, 7 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 7afb0760a1..fb49d19be1 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -322,13 +322,6 @@ def _create_layout(self, hide_help, **kwargs):
Field('last_hash'),
Field('licence'),
Field('subcategory', template='crispy/checkboxselectmultiple.html'),
- HTML(_(u"Demander de l'aide à la communauté !
"
- u"Si vous avez besoin d'un coup de main, "
- u"sélectionnez une ou plusieurs catégories d'aide ci-dessous "
- u'et votre contenu apparaîtra alors sur la page d\'aide.
')),
- Field('helps'),
Field('msg_commit'),
ButtonHolder(
StrictButton('Valider', type='submit'),
From 43a76af3805d4ec383f6c5390ca93721224f0ebd Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Sat, 22 Apr 2017 14:44:48 +0200
Subject: [PATCH 41/78] Update main.scss
---
assets/scss/main.scss | 1 -
1 file changed, 1 deletion(-)
diff --git a/assets/scss/main.scss b/assets/scss/main.scss
index 3c238f49f1..302babdec4 100644
--- a/assets/scss/main.scss
+++ b/assets/scss/main.scss
@@ -58,7 +58,6 @@
@import "components/alert-box";
@import "components/authors";
@import "components/autocomplete";
-@import "components/auto-merge";
@import "components/breadcrumb";
@import "components/codemirror";
@import "components/content-item";
From dd0444c761f079b7d522d258d03cdb01f34f5fe7 Mon Sep 17 00:00:00 2001
From: Antonin
Date: Tue, 9 May 2017 19:55:53 +0000
Subject: [PATCH 42/78] Fix styles, add sccs files
---
assets/scss/components/_auto-merge.scss | 3 +
assets/scss/components/_codemirror.scss | 334 ++++++++++++++++++++++++
assets/scss/components/_mergely.scss | 50 ++++
zds/tutorialv2/forms.py | 22 +-
zds/utils/forms.py | 4 +-
5 files changed, 400 insertions(+), 13 deletions(-)
create mode 100644 assets/scss/components/_auto-merge.scss
create mode 100644 assets/scss/components/_codemirror.scss
create mode 100644 assets/scss/components/_mergely.scss
diff --git a/assets/scss/components/_auto-merge.scss b/assets/scss/components/_auto-merge.scss
new file mode 100644
index 0000000000..9e1da08346
--- /dev/null
+++ b/assets/scss/components/_auto-merge.scss
@@ -0,0 +1,3 @@
+#compare-lhs-margin, #compare-rhs-margin {
+ display : none;
+}
\ No newline at end of file
diff --git a/assets/scss/components/_codemirror.scss b/assets/scss/components/_codemirror.scss
new file mode 100644
index 0000000000..1067b3ee6b
--- /dev/null
+++ b/assets/scss/components/_codemirror.scss
@@ -0,0 +1,334 @@
+/* BASICS */
+
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px;
+ color: black;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+ white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+ border-left: 1px solid black;
+ border-right: none;
+ width: 0;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.cm-fat-cursor .CodeMirror-cursor {
+ width: auto;
+ border: 0;
+ background: #7e7;
+}
+.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+@-moz-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@-webkit-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror-overwrite .CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+ background: white;
+}
+
+.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actual scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ display: inline-block;
+ margin-bottom: -30px;
+ /* Hack to make IE7 behave */
+ *zoom:1;
+ *display:inline;
+}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+.CodeMirror-gutter-wrapper {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+}
+
+.CodeMirror-cursor { position: absolute; }
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
+}
+div.CodeMirror-dragcursors {
+ visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursors {
+ visibility: hidden;
+ }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
diff --git a/assets/scss/components/_mergely.scss b/assets/scss/components/_mergely.scss
new file mode 100644
index 0000000000..832762d7b8
--- /dev/null
+++ b/assets/scss/components/_mergely.scss
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2016 by Jamie Peabody, http://www.mergely.com
+ * All rights reserved.
+ * Version: 3.4.3 2016-09-07
+ */
+
+/* required */
+.mergely-column textarea { width: 80px; height: 200px; }
+.mergely-column { float: left; }
+.mergely-margin { float: left; }
+.mergely-canvas { float: left; width: 28px; }
+
+/* resizeable */
+.mergely-resizer { width: 100%; height: 100%; }
+
+/* style configuration */
+.mergely-column { border: 1px solid #ccc; }
+.mergely-active { border: 1px solid #a3d1ff; }
+
+.mergely.a,.mergely.d,.mergely.c { color: #000; }
+
+.mergely.a.rhs.start { border-top: 1px solid #a3d1ff; }
+.mergely.a.lhs.start.end,
+.mergely.a.rhs.end { border-bottom: 1px solid #a3d1ff; }
+.mergely.a.rhs { background-color: #ddeeff; }
+.mergely.a.lhs.start.end.first { border-bottom: 0; border-top: 1px solid #a3d1ff; }
+
+.mergely.d.lhs { background-color: #ffe9e9; }
+.mergely.d.lhs.end,
+.mergely.d.rhs.start.end { border-bottom: 1px solid #f8e8e8; }
+.mergely.d.rhs.start.end.first { border-bottom: 0; border-top: 1px solid #f8e8e8; }
+.mergely.d.lhs.start { border-top: 1px solid #f8e8e8; }
+
+.mergely.c.lhs,
+.mergely.c.rhs { background-color: #fafafa; }
+.mergely.c.lhs.start,
+.mergely.c.rhs.start { border-top: 1px solid #a3a3a3; }
+.mergely.c.lhs.end,
+.mergely.c.rhs.end { border-bottom: 1px solid #a3a3a3; }
+
+.mergely.ch.a.rhs { background-color: #ddeeff; }
+.mergely.ch.d.lhs { background-color: #ffe9e9; text-decoration: line-through; color: red !important; }
+
+.mergely.current.start { border-top: 1px solid #000 !important; }
+.mergely.current.end { border-bottom: 1px solid #000 !important; }
+.mergely.current.lhs.a.start.end,
+.mergely.current.rhs.d.start.end { border-top: 0 !important; }
+.mergely.current.CodeMirror-linenumber { color: #F9F9F9; font-weight: bold; background-color: #777; }
+.CodeMirror-linenumber { cursor: pointer; }
+.CodeMirror-code { color: #717171; }
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index a08321b4dc..9cdc8c209d 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -162,9 +162,9 @@ def __init__(self, *args, **kwargs):
self.helper.layout.append(Layout(HTML('')))
self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
- css_class='btn btn-submit merge-btn need-to-merge-introduction'))))
-
+ ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
+ css_class='btn btn-submit merge-btn need-to-merge-introduction'))))
+
old_conclusion = kwargs.get('data').get('conclusion')
self.helper.layout.append(Layout(Field('conclusion', css_class='hidden')))
@@ -176,17 +176,17 @@ def __init__(self, *args, **kwargs):
ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
else:
-
+
self.helper.layout.append(Layout(
Field('introduction', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
- css_class='btn btn-grey preview-btn'),),
- HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
+ css_class='btn btn-grey preview-btn'),),
+ HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html"
with text=form.introduction.value %}{% endif %}'),
Field('conclusion', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),)))
- HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
+ HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html"
with text=form.conclusion.value %}{% endif %}'),
self.helper.layout.append(Layout(
@@ -285,7 +285,7 @@ def _create_layout(self, hide_help):
self.helper.layout.append(Layout(
ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
css_class='btn btn-submit merge-btn need-to-merge-introduction'))))
-
+
old_conclusion = kwargs.get('data').get('conclusion')
self.helper.layout.append(Layout(Field('conclusion', css_class='hidden')))
@@ -297,17 +297,17 @@ def _create_layout(self, hide_help):
ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
else:
-
+
self.helper.layout.append(Layout(
Field('introduction', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),),
- HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
+ HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html"
with text=form.introduction.value %}{% endif %}'),
Field('conclusion', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),)))
- HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
+ HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html"
with text=form.conclusion.value %}{% endif %}'),
self.helper.layout.append(Layout(
diff --git a/zds/utils/forms.py b/zds/utils/forms.py
index d619a3c309..bcbf45a46f 100644
--- a/zds/utils/forms.py
+++ b/zds/utils/forms.py
@@ -47,8 +47,8 @@ def __init__(self, *args, **kwargs):
text_field += HTML('' + old_text + '
')
text_field += HTML('')
- text_field+= ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
- css_class='btn btn-submit merge-btn need-to-merge-text'))
+ text_field += ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
+ css_class='btn btn-submit merge-btn need-to-merge-text'))
else:
text_field = Field('text', css_class='md-editor')
From c30ad5f89838edfcbcab3eb3ca36cf506fcd60c0 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 01:51:20 -0400
Subject: [PATCH 43/78] Update models.py
---
zds/forum/models.py | 20 --------------------
1 file changed, 20 deletions(-)
diff --git a/zds/forum/models.py b/zds/forum/models.py
index d6b8bd3626..9ce8d6b406 100644
--- a/zds/forum/models.py
+++ b/zds/forum/models.py
@@ -407,25 +407,6 @@ def old_post_warning(self):
return False
-<<<<<<< HEAD
- @staticmethod
- def has_read_permission(request):
- return True
-
- def has_object_read_permission(self, request):
- return Topic.has_read_permission(request)
-
- @staticmethod
- def has_write_permission(request):
- return request.user.is_authenticated()
-
- def has_object_write_permission(self, request):
- return Topic.has_write_permission(request)
-
- def has_object_update_permission(self, request):
- return Topic.has_write_permission(request) and (Topic.author == request.user)
-
-=======
@classmethod
def get_es_mapping(cls):
es_mapping = super(Topic, cls).get_es_mapping()
@@ -488,7 +469,6 @@ def save(self, *args, **kwargs):
def delete_topic_in_elasticsearch(sender, instance, **kwargs):
"""catch the pre_delete signal to ensure the deletion in ES"""
return delete_document_in_elasticsearch(instance)
->>>>>>> 943e338702bea9efed00c617ac3e46d5f85448ae
@python_2_unicode_compatible
From ca9952dd76c233763e374dd0511f1de738ccd582 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 12:58:30 +0200
Subject: [PATCH 44/78] Update models.py
---
zds/forum/models.py | 35 -----------------------------------
1 file changed, 35 deletions(-)
diff --git a/zds/forum/models.py b/zds/forum/models.py
index 9ce8d6b406..8d51803d50 100644
--- a/zds/forum/models.py
+++ b/zds/forum/models.py
@@ -14,7 +14,6 @@
from elasticsearch_dsl.field import Text, Keyword, Integer, Boolean, Float, Date
-from zds.member.api.permissions import CanReadTopic, CanReadPost, CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, IsOwnerOrReadOnly, IsStaffUser
from zds.forum.managers import TopicManager, ForumManager, PostManager, TopicReadManager
from zds.notification import signals
from zds.settings import ZDS_APP
@@ -160,10 +159,6 @@ def can_read(self, user):
pk=self.pk).exists()
else:
return False
-
- @staticmethod
- def has_write_permission(request):
- return request.user.has_perm("member.change_forum")
@property
def has_group(self):
@@ -502,35 +497,6 @@ def get_absolute_url(self):
def get_notification_title(self):
return self.topic.title
-<<<<<<< HEAD
- def is_author(self, user):
- """
- Check if the user given is the author of the message.
-
- :param user: Potential author of the message.
- :return: true if the user is the author.
- """
- return self.author == user
-
- @staticmethod
- def has_read_permission(request):
- return True
-
- def has_object_read_permission(self, request):
- return Post.has_read_permission(request)
-
- @staticmethod
- def has_write_permission(request):
- return request.user.is_authenticated() and request.user.profile.can_write_now()
-
- def has_object_write_permission(self, request):
- return Topic.has_write_permission(request)
-
- def has_object_update_permission(self, request):
- return self.is_author(request.user)
- # TODO peut on editer quand un topic est ferme ?
- # TODO a tester, l'auteur avait acces a ubn forum prive, mais ce n'est plus le cas, peut il editer ses messages
-=======
@classmethod
def get_es_mapping(cls):
es_mapping = super(Post, cls).get_es_mapping()
@@ -600,7 +566,6 @@ def delete_post_in_elasticsearch(sender, instance, **kwargs):
"""catch the pre_delete signal to ensure the deletion in ES"""
return delete_document_in_elasticsearch(instance)
->>>>>>> 943e338702bea9efed00c617ac3e46d5f85448ae
@python_2_unicode_compatible
class TopicRead(models.Model):
From e61d8cfa5232c88e2a57645eb544a93f16614d9a Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 13:13:29 +0200
Subject: [PATCH 45/78] Update models.py
---
zds/forum/models.py | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/zds/forum/models.py b/zds/forum/models.py
index 8d51803d50..040ae023c0 100644
--- a/zds/forum/models.py
+++ b/zds/forum/models.py
@@ -583,13 +583,12 @@ class Meta:
user = models.ForeignKey(User, related_name='topics_read', db_index=True)
objects = TopicReadManager()
- def __unicode__(self):
- return u''.format(self.topic,
- self.user,
- self.post.pk)
-
+ def __str__(self):
+ return "".format(self.topic,
+ self.user,
+ self.post.pk)
-def is_read(topic, user=None):
+ def is_read(topic, user=None):
"""
Checks if the user has read the **last post** of the topic.
Returns false if the user read the topic except its last post.
From 588b3a950d4ff5fa4c354f878b4451e7a9c3c0c5 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 13:37:03 +0200
Subject: [PATCH 46/78] Update models.py
---
zds/forum/models.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zds/forum/models.py b/zds/forum/models.py
index 040ae023c0..16d7a8a9af 100644
--- a/zds/forum/models.py
+++ b/zds/forum/models.py
@@ -588,7 +588,7 @@ def __str__(self):
self.user,
self.post.pk)
- def is_read(topic, user=None):
+def is_read(topic, user=None):
"""
Checks if the user has read the **last post** of the topic.
Returns false if the user read the topic except its last post.
From 32e5899d7ac6258cd2d085c05a1114f757cfbf3a Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 13:51:28 +0200
Subject: [PATCH 47/78] Update forms.py
---
zds/tutorialv2/forms.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 39a4298682..b172d427a6 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -185,12 +185,12 @@ def __init__(self, *args, **kwargs):
Field('introduction', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),),
- HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html"
+ HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
with text=form.introduction.value %}{% endif %}'),
Field('conclusion', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),)))
- HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html"
+ HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
with text=form.conclusion.value %}{% endif %}'),
self.helper.layout.append(Layout(
From 868945a637567f1d739bd47814a2d1448cddb29a Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 20:54:02 +0200
Subject: [PATCH 48/78] Update forms.py
---
zds/tutorialv2/forms.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index b172d427a6..a60f8ca82a 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -310,13 +310,13 @@ def _create_layout(self, hide_help, **kwargs):
Field('introduction', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),),
- HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html"
+ HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
with text=form.introduction.value %}{% endif %}'),
Field('conclusion', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),)))
- HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html"
+ HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
with text=form.conclusion.value %}{% endif %}'),
self.helper.layout.append(Layout(
From ab60b1378afc39bcd827a182e726ea15bd07be28 Mon Sep 17 00:00:00 2001
From: Antonin
Date: Wed, 10 May 2017 20:02:33 +0000
Subject: [PATCH 49/78] Remove files
---
zds/forum/api/permissions.py | 69 --
zds/forum/api/serializer.py | 168 ----
zds/forum/api/tests.py | 1684 ----------------------------------
zds/forum/api/urls.py | 16 -
zds/forum/api/views.py | 647 -------------
zds/utils/validators.py | 116 ---
6 files changed, 2700 deletions(-)
delete mode 100644 zds/forum/api/permissions.py
delete mode 100644 zds/forum/api/serializer.py
delete mode 100644 zds/forum/api/tests.py
delete mode 100644 zds/forum/api/urls.py
delete mode 100644 zds/forum/api/views.py
delete mode 100644 zds/utils/validators.py
diff --git a/zds/forum/api/permissions.py b/zds/forum/api/permissions.py
deleted file mode 100644
index e5798002d3..0000000000
--- a/zds/forum/api/permissions.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from rest_framework import permissions
-from django.contrib.auth.models import AnonymousUser
-from zds.forum.models import Forum, Topic
-from django.http import Http404
-
-
-class IsStaffUser(permissions.BasePermission):
- """
- Allows access only to staff users.
- """
-
- def has_permission(self, request, view):
- return request.user and request.user.is_staff
-
-
-class IsOwnerOrIsStaff(permissions.BasePermission):
- """
- Allows access only to staff users or object owner.
- """
-
- def has_object_permission(self, request, view, obj):
- # Read permissions are allowed to any request,
- # so we'll always allow GET, HEAD or OPTIONS requests.
- if request.method in permissions.SAFE_METHODS:
- return True
-
- # Write permissions are only allowed to the owner of the snippet
- if hasattr(obj, 'user'):
- author = obj.user
- elif hasattr(obj, 'author'):
- author = obj.author
- else:
- author = AnonymousUser()
-
- print(request.user)
- print(author)
- print(request.user.has_perm("forum.change_topic"))
- return (author == request.user) or (request.user.has_perm("forum.change_topic"))
-
-class CanWriteInForum(permissions.BasePermission):
- """
- Allows access only to people that can write in forum passed by post request.
- """
-
- def has_permission(self, request, view):
- print('can write forum')
- print(request.data)
- try:
- forum = Forum.objects.get(id=request.data.get('forum')) # TODO tester si on met un id qui n'existe pas
- except Forum.DoesNotExist:
- raise Http404("Forum with pk {} was not found".format(request.data.get('forum')))
-
- return forum.can_read(request.user)
-
-class CanWriteInTopic(permissions.BasePermission):
- """
- Allows access only to people that can write in topic passed by url.
- """
-
- def has_permission(self, request, view):
- print('can write topic')
- topic_pk = request.resolver_match.kwargs.get('pk')
- try:
- topic = Topic.objects.get(id=topic_pk) # TODO tester si on met un id qui n'existe pas
- except Topic.DoesNotExist:
- raise Http404("Topic with pk {} was not found".format(topic_pk))
- return topic.forum.can_read(request.user)
\ No newline at end of file
diff --git a/zds/forum/api/serializer.py b/zds/forum/api/serializer.py
deleted file mode 100644
index 47e1a03810..0000000000
--- a/zds/forum/api/serializer.py
+++ /dev/null
@@ -1,168 +0,0 @@
-from rest_framework import serializers
-from zds.forum.models import Forum, Topic, Post
-from zds.utils.models import Alert
-from dry_rest_permissions.generics import DRYPermissionsField
-from dry_rest_permissions.generics import DRYPermissions
-from django.shortcuts import get_object_or_404
-from zds.utils.validators import TitleValidator, TextValidator
-
-
-class ForumSerializer(serializers.ModelSerializer):
- class Meta:
- model = Forum
-
-class TopicSerializer(serializers.ModelSerializer):
- class Meta:
- model = Topic
- #fields = ('id', 'title', 'subtitle', 'slug', 'category', 'position_in_category')
- permissions_classes = DRYPermissions
-
-
-
-class TopicCreateSerializer(serializers.ModelSerializer, TitleValidator, TextValidator):
- """
- Serializer to create a new topic.
- """
- text = serializers.CharField()
- permissions = DRYPermissionsField()
-
- class Meta:
- model = Topic
- fields = ('id', 'title', 'subtitle', 'forum', 'text',
- 'author', 'last_message', 'pubdate',
- 'permissions', 'slug')
- read_only_fields = ('id', 'author', 'last_message', 'pubdate', 'permissions', 'slug')
-
- def create(self, validated_data):
-
- # Remember that text ist not a field for a Topic but for a post.
- text = validated_data.pop('text')
- new_topic = Topic.objects.create(**validated_data)
-
- # Instead we use text here.
- first_message = Post.objects.create(topic=new_topic,text=text,position=0,author=new_topic.author)
- new_topic.last_message = first_message
- new_topic.save()
-
- # And serve it here so it appears in response.
- new_topic.text = text
- return new_topic
-
-
-class TopicUpdateSerializer(serializers.ModelSerializer, TitleValidator, TextValidator):
- """
- Serializer to update a topic.
- """
- title = serializers.CharField(required=False, allow_blank=False)
- subtitle = serializers.CharField(required=False, allow_blank=True)
- permissions = DRYPermissionsField()
-
- class Meta:
- model = Topic
- fields = ('id', 'title', 'subtitle', 'permissions', 'forum', 'is_locked', 'is_solved', 'tags')
- read_only_fields = ('id', 'permissions', 'forum', 'is_locked')
-
- def update(self, instance, validated_data):
- for attr, value in validated_data.items():
- setattr(instance, attr, value)
- instance.save()
- return instance
-
- def throw_error(self, key=None, message=None):
- raise serializers.ValidationError(message)
-
-class TopicUpdateStaffSerializer(serializers.ModelSerializer, TitleValidator, TextValidator):
- """
- Serializer to update a topic by a staff member (extra rights).
- """
- title = serializers.CharField(required=False)
- forum = serializers.PrimaryKeyRelatedField(queryset=Forum.objects.all(), required=False)
- #subtitle = serializers.CharField(required=False, allow_blank=True)
- permissions = DRYPermissionsField()
-
-
- class Meta:
- model = Topic
- fields = ('id', 'title', 'subtitle', 'permissions', 'forum', 'is_locked', 'is_solved', 'tags')
- read_only_fields = ('id', 'permissions')
-
- def update(self, instance, validated_data):
- for attr, value in validated_data.items():
- setattr(instance, attr, value)
- instance.save()
- return instance
-
- def throw_error(self, key=None, message=None):
- raise serializers.ValidationError(message)
-
-class PostSerializer(serializers.ModelSerializer):
- class Meta:
- model = Post
- exclude = ('ip_address', 'text_hidden',)
- read_only = ('ip_address', 'text_hidden',)
- permissions_classes = DRYPermissions
-
-class PostCreateSerializer(serializers.ModelSerializer, TextValidator):
- """
- Serializer to send a post in a topic
- """
- permissions = DRYPermissionsField()
-
- class Meta:
- model = Post
- fields = ('id', 'text', 'text_html', 'permissions', 'is_useful', 'author', 'position', 'pubdate')
- read_only_fields = ('text_html', 'permissions', 'is_useful', 'author', 'position', 'pubdate')
- # TODO a voir quel champ en read only
-
- def create(self, validated_data):
- # Get topic
- pk_topic = validated_data.get('topic_id')
- topic = get_object_or_404(Topic, pk=(pk_topic))
- new_post = Post.objects.create(**validated_data)
- return new_post
-
- # Todo a t on besoin d'un validateur
- #def throw_error(self, key=None, message=None):
- #raise serializers.ValidationError(message)
-
-class PostUpdateSerializer(serializers.ModelSerializer, TextValidator):
- """
- Serializer to update a post.
- """
- text = serializers.CharField(required=True, allow_blank=True)
- permissions = DRYPermissionsField()
-
- class Meta:
- model = Topic
- fields = ('id', 'text', 'permissions',)
- read_only_fields = ('id', 'permissions',)
-
- def update(self, instance, validated_data):
- for attr, value in validated_data.items():
- setattr(instance, attr, value)
- instance.save()
- return instance
-
-
-class AlertSerializer(serializers.ModelSerializer):
- """
- Serializer to alert a post.
- """
- permissions = DRYPermissionsField()
-
- class Meta:
- model = Alert
- fields = ('id', 'text', 'permissions',)
- read_only_fields = ('permissions',)
-
-
- def create(self, validated_data):
- # Get topic TODO pourquoi ces lignes?
- pk_post = validated_data.get('comment')
- post = get_object_or_404(Post, pk=(pk_post))
- alert = Alert.objects.create(**validated_data)
- return alert
-
- # Todo a t on besoin d'un validateur ?
- #def throw_error(self, key=None, message=None):
- #raise serializers.ValidationError(message)
\ No newline at end of file
diff --git a/zds/forum/api/tests.py b/zds/forum/api/tests.py
deleted file mode 100644
index c8cdf3d6cd..0000000000
--- a/zds/forum/api/tests.py
+++ /dev/null
@@ -1,1684 +0,0 @@
-# coding: utf-8
-
-from django.conf import settings
-from django.core.cache import caches
-from django.core.urlresolvers import reverse
-from django.contrib.auth.models import Group
-from django.db import transaction
-from rest_framework import status
-from rest_framework.test import APIClient
-from rest_framework.test import APITestCase
-from oauth2_provider.models import Application, AccessToken
-from rest_framework_extensions.settings import extensions_api_settings
-from zds.api.pagination import REST_PAGE_SIZE, REST_MAX_PAGE_SIZE, REST_PAGE_SIZE_QUERY_PARAM
-from zds.member.factories import ProfileFactory, StaffProfileFactory
-from zds.forum.models import Forum, Topic, Post
-from zds.forum.factories import PostFactory
-from zds.forum.tests.tests_views import create_category, add_topic_in_a_forum
-from zds.utils.models import CommentVote, Alert
-
-
-class ForumPostKarmaAPITest(APITestCase):
- def setUp(self):
-
- self.client = APIClient()
- self.profile = ProfileFactory()
-
- client_oauth2 = create_oauth2_client(self.profile.user)
- self.client_authenticated = APIClient()
- authenticate_client(self.client_authenticated, client_oauth2, self.profile.user.username, 'hostel77')
-
- caches[extensions_api_settings.DEFAULT_USE_CACHE].clear()
-
- def test_failure_post_karma_with_client_unauthenticated(self):
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- post = PostFactory(topic=topic, author=profile.user, position=2)
-
- response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- def test_failure_post_karma_with_sanctioned_user(self):
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- another_profile = ProfileFactory()
- post = PostFactory(topic=topic, author=another_profile.user, position=2)
-
- profile = ProfileFactory()
- profile.can_read = False
- profile.can_write = False
- profile.save()
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)))
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_failure_post_karma_with_a_message_not_found(self):
- response = self.client.get(reverse('api:forum:post-karma', args=(99999,)))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_failure_post_karma_of_a_forum_we_cannot_read(self):
- group = Group.objects.create(name='DummyGroup_1')
-
- profile = ProfileFactory()
- category, forum = create_category(group)
- topic = add_topic_in_a_forum(forum, profile)
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.put(reverse('api:forum:post-karma', args=(topic.last_message.pk,)), {'vote': 'like'})
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_success_post_karma_like(self):
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- another_profile = ProfileFactory()
- post = PostFactory(topic=topic, author=another_profile.user, position=2)
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)), {'vote': 'like'})
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(CommentVote.objects.filter(user=profile.user, comment=post, positive=True).exists())
-
- def test_success_post_karma_dislike(self):
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- another_profile = ProfileFactory()
- post = PostFactory(topic=topic, author=another_profile.user, position=2)
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)), {'vote': 'dislike'})
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(CommentVote.objects.filter(user=profile.user, comment=post, positive=False).exists())
-
- def test_success_post_karma_neutral(self):
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- another_profile = ProfileFactory()
- post = PostFactory(topic=topic, author=another_profile.user, position=2)
-
- vote = CommentVote(user=profile.user, comment=post, positive=True)
- vote.save()
-
- self.assertTrue(CommentVote.objects.filter(pk=vote.pk).exists())
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)), {'vote': 'neutral'})
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertFalse(CommentVote.objects.filter(pk=vote.pk).exists())
-
- def test_success_post_karma_like_already_disliked(self):
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- another_profile = ProfileFactory()
- post = PostFactory(topic=topic, author=another_profile.user, position=2)
-
- vote = CommentVote(user=profile.user, comment=post, positive=False)
- vote.save()
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)), {'vote': 'like'})
- vote.refresh_from_db()
-
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(vote.positive)
-
- def test_get_post_voters(self):
- profile = ProfileFactory()
- profile2 = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- another_profile = ProfileFactory()
-
- upvoted_answer = PostFactory(topic=topic, author=another_profile.user, position=2)
- upvoted_answer.like += 2
- upvoted_answer.save()
- CommentVote.objects.create(user=profile.user, comment=upvoted_answer, positive=True)
-
- downvoted_answer = PostFactory(topic=topic, author=another_profile.user, position=3)
- downvoted_answer.dislike += 2
- downvoted_answer.save()
- anon_limit = CommentVote.objects.create(user=profile.user, comment=downvoted_answer, positive=False)
-
- CommentVote.objects.create(user=profile2.user, comment=upvoted_answer, positive=True)
- CommentVote.objects.create(user=profile2.user, comment=downvoted_answer, positive=False)
-
- equal_answer = PostFactory(topic=topic, author=another_profile.user, position=4)
- equal_answer.like += 1
- equal_answer.dislike += 1
- equal_answer.save()
- CommentVote.objects.create(user=profile.user, comment=equal_answer, positive=True)
- CommentVote.objects.create(user=profile2.user, comment=equal_answer, positive=False)
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
-
- # on first message we should see 2 likes and 0 anonymous
- response = self.client.get(reverse('api:forum:post-karma', args=[upvoted_answer.pk]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(2, len(response.data['like']['users']))
- self.assertEqual(0, len(response.data['dislike']['users']))
- self.assertEqual(2, response.data['like']['count'])
- self.assertEqual(0, response.data['dislike']['count'])
-
- # on second message we should see 2 dislikes and 0 anonymous
- response = self.client.get(reverse('api:forum:post-karma', args=[downvoted_answer.pk]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(0, len(response.data['like']['users']))
- self.assertEqual(2, len(response.data['dislike']['users']))
- self.assertEqual(0, response.data['like']['count'])
- self.assertEqual(2, response.data['dislike']['count'])
-
- # on third message we should see 1 like and 1 dislike and 0 anonymous
- response = self.client.get(reverse('api:forum:post-karma', args=[equal_answer.pk]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(1, len(response.data['like']['users']))
- self.assertEqual(1, len(response.data['dislike']['users']))
- self.assertEqual(1, response.data['like']['count'])
- self.assertEqual(1, response.data['dislike']['count'])
-
- # Now we change the settings to keep anonymous the first [dis]like
- settings.VOTES_ID_LIMIT = anon_limit.pk
- # and we run the same tests
- # on first message we should see 1 like and 1 anonymous
- response = self.client.get(reverse('api:forum:post-karma', args=[upvoted_answer.pk]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(1, len(response.data['like']['users']))
- self.assertEqual(0, len(response.data['dislike']['users']))
- self.assertEqual(2, response.data['like']['count'])
- self.assertEqual(0, response.data['dislike']['count'])
-
- # on second message we should see 1 dislikes and 1 anonymous
- response = self.client.get(reverse('api:forum:post-karma', args=[downvoted_answer.pk]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(0, len(response.data['like']['users']))
- self.assertEqual(1, len(response.data['dislike']['users']))
- self.assertEqual(0, response.data['like']['count'])
- self.assertEqual(2, response.data['dislike']['count'])
-
- # on third message we should see 1 like and 1 dislike and 0 anonymous
- response = self.client.get(reverse('api:forum:post-karma', args=[equal_answer.pk]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(1, len(response.data['like']['users']))
- self.assertEqual(1, len(response.data['dislike']['users']))
- self.assertEqual(1, response.data['like']['count'])
- self.assertEqual(1, response.data['dislike']['count'])
-
-
-class ForumAPITest(APITestCase):
- def setUp(self):
- self.client = APIClient()
-
- self.profile = ProfileFactory()
- client_oauth2 = create_oauth2_client(self.profile.user)
- self.client_authenticated = APIClient()
- authenticate_client(self.client_authenticated, client_oauth2, self.profile.user.username, 'hostel77')
-
- self.staff = StaffProfileFactory()
- client_oauth2 = create_oauth2_client(self.staff.user)
- self.client_authenticated_staff = APIClient()
- authenticate_client(self.client_authenticated_staff, client_oauth2, self.staff.user.username, 'hostel77')
-
- self.group_staff = Group.objects.filter(name="staff").first()
-
- caches[extensions_api_settings.DEFAULT_USE_CACHE].clear()
-
- def create_multiple_forums(self, number_of_forum=REST_PAGE_SIZE):
- for forum in xrange(0, number_of_forum):
- category, forum = create_category()
-
- def create_multiple_forums_with_topics(self, number_of_forum=REST_PAGE_SIZE, number_of_topic=REST_PAGE_SIZE, profile=None):
- if profile is None:
- profile = ProfileFactory()
- for forum in xrange(0, number_of_forum):
- category, forum = create_category()
- for topic in xrange(0, number_of_topic):
- new_topic = add_topic_in_a_forum(forum, profile)
- if number_of_forum == 1 and number_of_topic == 1:
- return new_topic
-
- def create_topic_with_post(self, number_of_post=REST_PAGE_SIZE, profile=None):
- if profile is None:
- profile = ProfileFactory()
-
- category, forum = create_category()
- new_topic = add_topic_in_a_forum(forum, profile)
- posts = []
-
- for post in xrange(0, number_of_post):
- posts.append(PostFactory(topic=new_topic, author=profile.user, position=2))
-
- return new_topic, posts
-
- def test_list_of_forums_empty(self):
- """
- Gets empty list of forums in the database.
- """
- response = self.client.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_forums(self):
- """
- Gets list of forums not empty in the database.
- """
- self.create_multiple_forums()
-
- response = self.client.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_forums_private(self):
- """
- Gets list of private forums not empty in the database (only for staff).
- """
- category, forum = create_category(self.group_staff)
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
-
- response = self.client_authenticated.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
-
- response = self.client_authenticated_staff.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 1) # TODO nombre a affiner en fonction de la realite
-
-
- def test_list_of_forums_with_several_pages(self):
- """
- Gets list of forums with several pages in the database.
- """
- self.create_multiple_forums(REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client.get(reverse('api:forum:list') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 1)
-
- def test_list_of_forums_for_a_page_given(self):
- """
- Gets list of forums with several pages and gets a page different that the first one.
- """
- self.create_multiple_forums(REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 11)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_forums_for_a_wrong_page_given(self):
- """
- Gets an error when the forums asks a wrong page.
- """
- response = self.client.get(reverse('api:forum:list') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_forums_with_a_custom_page_size(self):
- """
- Gets list of forums with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- self.create_multiple_forums(REST_PAGE_SIZE * 2)
-
- page_size = 'page_size'
- response = self.client.get(reverse('api:forum:list') + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_forums_with_a_wrong_custom_page_size(self):
- """
- Gets list of forums with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- self.create_multiple_forums(page_size_value)
-
- response = self.client.get(reverse('api:forum:list') + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), page_size_value)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_details_forum(self):
- """
- Tries to get the details of a forum.
- """
-
- category, forum = create_category()
- response = self.client.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('id'), forum.id)
- self.assertEqual(response.data.get('title'), forum.title)
- self.assertEqual(response.data.get('subtitle'), forum.subtitle)
- self.assertEqual(response.data.get('slug'), forum.slug)
- self.assertEqual(response.data.get('category'), forum.category.id)
- self.assertEqual(response.data.get('position_in_category'), forum.position_in_category)
-
- print('-------')
- print(type(response.data.get('group')))
- print(type(list(forum.group.all())))
- print('-------')
- self.assertEqual(response.data.get('group'), list(forum.group.all()))
-
-
- def test_details_forum_private(self):
- """
- Tries to get the details of a private forum with different users.
- """
- category, forum = create_category(self.group_staff)
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- response = self.client_authenticated_staff.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
- def test_details_unknown_forum(self):
- """
- Tries to get the details of a forum that does not exists.
- """
-
- self.create_multiple_forums(1)
- response = self.client.get(reverse('api:forum:detail', args=[3]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_details_private_forum_user(self):
- """
- Tries to get the details of a private forum with a normal user, staff user and anonymous one.
- """
- category, forum = create_category(self.group_staff)
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- response = self.client_authenticated_staff.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
-# TODO
-# Récupérer la liste des sujets en filtrant sur le tag (resulat non vide)
-# Récupérer la liste des sujets en filtrant tag, forum, auteur (resulat non vide)
-
- def test_list_of_topics_empty(self):
- """
- Gets empty list of topics in the database.
- """
- response = self.client.get(reverse('api:forum:list-topic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_topics(self):
- """
- Gets list of topics not empty in the database.
- """
- self.create_multiple_forums_with_topics(1)
- response = self.client.get(reverse('api:forum:list-topic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_topics_with_several_pages(self):
- """
- Gets list of topics with several pages in the database.
- """
- self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list-topic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client.get(reverse('api:forum:list-topic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 1)
-
- def test_list_of_topics_for_a_page_given(self):
- """
- Gets list of topics with several pages and gets a page different that the first one.
- """
- self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list-topic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 11)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_topics_for_a_wrong_page_given(self):
- """
- Gets an error when the topics asks a wrong page.
- """
- response = self.client.get(reverse('api:forum:list-topic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_topics_with_a_custom_page_size(self):
- """
- Gets list of topics with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE * 2)
-
- page_size = 'page_size'
- response = self.client.get(reverse('api:forum:list-topic') + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_topics_with_a_wrong_custom_page_size(self):
- """
- Gets list of topics with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- self.create_multiple_forums_with_topics(1, page_size_value)
-
- response = self.client.get(reverse('api:forum:list-topic') + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), page_size_value)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_list_of_topics_with_forum_filter_empty(self):
- """
- Gets an empty list of topics in a forum.
- """
- self.create_multiple_forums_with_topics(1)
- response = self.client.get(reverse('api:forum:list-topic') + '?forum=3')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_topics_with_forum_filter(self):
- """
- Gets a list of topics in a forum.
- """
- self.create_multiple_forums_with_topics(1)
- forum = Forum.objects.all().first()
- response = self.client.get(reverse('api:forum:list-topic') + '?forum=' + str(forum.id))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- def test_list_of_topics_with_author_filter_empty(self):
- """
- Gets an empty list of topics created by an user.
- """
- self.create_multiple_forums_with_topics(1)
- response = self.client.get(reverse('api:forum:list-topic') + '?author=6')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_topics_with_author_filter(self):
- """
- Gets a list of topics created by an user.
- """
- self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE, self.profile)
- response = self.client.get(reverse('api:forum:list-topic') + '?author=' + str(self.profile.user.id))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- def test_list_of_topics_with_tag_filter_empty(self):
- """
- Gets an empty list of topics with a specific tag.
- """
- self.create_multiple_forums_with_topics(1)
- response = self.client.get(reverse('api:forum:list-topic') + '?tags__title=ilovezozor')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_new_topic_with_user(self):
- """
- Post a new topic in a forum with an user.
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- topic = Topic.objects.filter(author=self.profile.user.id).first()
- self.assertEqual(response.data.get('title'), topic.title)
- self.assertEqual(response.data.get('subtitle'), topic.subtitle)
- self.assertEqual(data.get('text'), topic.last_message.text)
- self.assertEqual(response.data.get('author'), self.profile.user.id)
- self.assertIsNotNone(response.data.get('last_message'))
- self.assertIsNotNone(response.data.get('pubdate'))
-
- def test_new_topic_with_anonymous(self):
- """
- Post a new topic in a forum with an anonymous user.
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
- self.client = APIClient()
- response = self.client.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- def test_new_topic_private_forum(self):
- """
- Post a new topic in a private forum (staff only) with an anonymous user, normal user and staff user.
- """
- profile = ProfileFactory()
- self.group_staff.user_set.add(profile.user)
- category, forum = create_category(self.group_staff)
- data = {
- 'title': 'Have you seen the guy flooding ?',
- 'subtitle': 'He is asking to many question about flask.',
- 'text': 'Should we ban him ? I think we should.',
- 'forum': forum.id
- }
-
- # Anonymous
- self.client = APIClient()
- response = self.client.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # User
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff
- response = self.client_authenticated_staff.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- def test_new_topic_with_banned_user(self):
-
- profile = ProfileFactory()
- profile.can_read = False
- profile.can_write = False
- profile.save()
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_new_topic_without_title(self):
- """
- Try to post a new topic in a forum without the title
- """
- self.create_multiple_forums()
- data = {
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_new_topic_without_subtitle(self):
- """
- Try to post a new topic in a forum without the title
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- def test_new_topic_without_text(self):
- """
- Try to post a new topic in a forum without the text.
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'forum': 1
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_new_topic_in_unknow_forum(self):
- """
- Try to post a new topic in a forum that does not exists.
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 666
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_details_topic(self):
- """
- Get details of a topic.
- """
- topic = self.create_multiple_forums_with_topics(1, 1)
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail-topic', args=(topic.id,)))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('title'), topic.title)
- self.assertEqual(response.data.get('subtitle'), topic.subtitle)
- self.assertEqual(response.data.get('forum'), topic.forum.id)
- self.assertIsNotNone(response.data.get('title'))
- self.assertIsNotNone(response.data.get('forum'))
-
- def test_details_unknown_topic(self):
- """
- Get details of a topic that doesw not exist.
- """
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail-topic', args=[666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_details_topic_private(self):
- """
- Tries to get details of a topic that is in a private forum.
- """
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, self.staff)
-
- # Anonymous
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail-topic', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # User
- response = self.client_authenticated.get(reverse('api:forum:detail-topic', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff
- response = self.client_authenticated_staff.get(reverse('api:forum:detail-topic', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
-
-# Édite un sujet qvec user en ls
-# Édite un sujet avec user banni
-# Editer dans un forum privé ? Verifier les auths
-# TODO
-
- def test_update_topic_details_title(self):
- """
- Updates title of a topic.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- print('test_update_topic_details_title')
- print(response)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('title'), data.get('title'))
-
- def test_update_topic_details_title_empty(self):
- """
- Updates title of a topic, tries to put an empty sting
- """
- data = {
- 'title': ''
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_update_topic_details_subtitle(self):
- """
- Updates subtitle of a topic.
- """
- data = {
- 'subtitle': 'Mon nouveau sous-titre'
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('subtitle'), data.get('subtitle'))
-
- def test_update_topic_anonymous(self):
- """
- Tries to update a Topic with an anonymous user.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- topic = self.create_multiple_forums_with_topics(1, 1)
- self.client = APIClient()
- response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- def test_update_topic_staff(self):
- """
- Updates title of a topic with a staff member.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('title'), data.get('title'))
-
- def test_update_topic_other_user(self):
- """
- Tries to update title of a topic posted by another user.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- profile = ProfileFactory()
- topic = self.create_multiple_forums_with_topics(1, 1, profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_update_unknown_topic(self):
- """
- Tries to update title of a non existing topic.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[666]), data)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_update_topic_forum(self):
- """
- Tries to move (change forum in which the topic is) with different users.
- """
- data = {
- 'forum': 5
- }
- profile = ProfileFactory()
- self.create_multiple_forums_with_topics(5, 1, profile)
- topic = Topic.objects.filter(forum=1).first()
-
- # Anonymous
- self.client = APIClient()
- response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # User
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('forum'), data.get('forum'))
-
- def test_update_topic_lock(self):
- """
- Tries to lock a Topic with different users.
- """
- data = {
- 'is_locked': True
- }
- topic = self.create_multiple_forums_with_topics(1, 1)
-
- self.client = APIClient()
- response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(response.data.get('is_locked'))
-
- def test_update_topic_solve(self):
- """
- Tries to solve a Topic with different users.
- """
- data = {
- 'is_solved': True
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
-
- self.client = APIClient()
- response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # Author
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(response.data.get('is_solved'))
-
- # Other user
- other_profile = ProfileFactory()
- client_oauth2 = create_oauth2_client(other_profile.user)
- client_other_user = APIClient()
- authenticate_client(client_other_user, client_oauth2, other_profile.user.username, 'hostel77')
-
- response = client_other_user.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(response.data.get('is_solved'))
-
- def test_list_of_posts_unknown(self):
- """
- Tries to get a list of posts in an unknown topic
- """
- response = self.client.get(reverse('api:forum:list-post', args=[666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_posts(self):
- """
- Gets list of posts in a topic.
- """
- # A post is already included with the topic
- topic, posts = self.create_topic_with_post(REST_PAGE_SIZE - 1)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_posts_private_forum(self):
- """
- Get a list of posts in a topic of a private forum.
- """
- group = Group.objects.create(name="DummyGroup_1")
-
- profile = ProfileFactory()
- group.user_set.add(profile.user)
- category, forum = create_category(group)
- topic = add_topic_in_a_forum(forum, profile)
- # def add_topic_in_a_forum(forum, profile, is_sticky=False, is_solved=False, is_locked=False):
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 1)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_posts_private_forum_user(self):
- """
- Tries to get a list of posts in a topic of a private forum with a normal user.
- """
- profile = ProfileFactory()
- self.group_staff.user_set.add(profile.user)
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-post', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 1)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_posts_with_several_pages(self):
- """
- Gets list of posts with several pages in the database.
- """
- topic, posts = self.create_topic_with_post(REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 2) # Note : when creating a Topic a first post is created, explaining the +1
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 2)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 2)
-
- def test_list_of_posts_for_a_page_given(self):
- """
- Gets list of posts with several pages and gets a page different that the first one.
- """
- topic, posts = self.create_topic_with_post(REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 12)
- self.assertEqual(len(response.data.get('results')), 2)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_posts_for_a_wrong_page_given(self):
- """
- Gets an error when the posts asks a wrong page.
- """
- topic, posts = self.create_topic_with_post(1)
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_posts_with_a_custom_page_size(self):
- """
- Gets list of forums with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- topic, posts = self.create_topic_with_post((REST_PAGE_SIZE * 2) - 1)
- print (topic)
-
- page_size = 'page_size'
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_posts_in_topic_with_a_wrong_custom_page_size(self):
- """
- Gets list of posts with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- topic, posts = self.create_topic_with_post(page_size_value)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
- # We will have page_size_value + 1 because a post is added at topic creation.
- self.assertEqual(response.data.get('count'), page_size_value + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_list_of_posts_in_unknown_topic(self):
- """
- Tries to list the posts of an non existing Topic.
- """
-
- response = self.client.get(reverse('api:forum:list-post', args=[666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_user_topics_empty(self):
- """
- Gets empty list of topic that the user created.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_user_topics(self):
- """
- Gets list of user's topics not empty in the database.
- """
- self.create_multiple_forums_with_topics(10, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 10)
- self.assertEqual(len(response.data.get('results')), 10)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_user_topics_with_several_pages(self):
- """
- Gets list of user's topics with several pages in the database.
- """
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 1)
-
- def test_list_of_user_topics_for_a_page_given(self):
- """
- Gets list of user's topics with several pages and gets a page different that the first one.
- """
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 11)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_users_topics_for_a_wrong_page_given(self):
- """
- Gets an error when the user's topics asks a wrong page.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_user_topics_with_a_custom_page_size(self):
- """
- Gets list of user's topics with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE * 2, 1, self.profile)
-
- page_size = 'page_size'
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_user_topics_with_a_wrong_custom_page_size(self):
- """
- Gets list of user's topic with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- self.create_multiple_forums_with_topics(page_size_value, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), page_size_value)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_list_of_user_topics_anonymous(self):
- """
- Tries to get a list of users topic with an anonymous user.
- """
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:list-usertopic'))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
-
-# DONE Créer un message 200
-# DONE Créer un message avec un contenu vide
-# DONECréer un message dans un sujet qui n'existe pas
-# DONE Créer un message en anonymous
-# DONE Créer un message dans un forum privé en user
-# DONE Créer un message dans un forum privé en staff
-# Créer un message dans un sujet fermé en user
-# Créer un message dans un sujet fermé en staff
-# Créer un message pour tester l'antiflood
-
-
- def test_create_post_with_no_field(self):
- """
- Creates a post in a topic but not with according field.
- """
- topic = self.create_multiple_forums_with_topics(1, 1)
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.id]), {})
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_create_post_with_empty_field(self):
- """
- Creates a post in a topic but with no text.
- """
- data = {
- 'text': ''
- }
- topic = self.create_multiple_forums_with_topics(1, 1)
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_create_post_unauthenticated(self):
- """
- Creates a post in a topic with unauthenticated client.
- """
- data = {
- 'text': 'Welcome to this post!'
- }
-
- topic = self.create_multiple_forums_with_topics(1, 1)
- self.client = APIClient()
- with transaction.atomic():
- response = self.client.post(reverse('api:forum:list-post', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- def test_create_post_with_bad_topic_id(self):
- """
- Creates a post in a topic with a bad topic id.
- """
- data = {
- 'text': 'Welcome to this post!'
- }
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[666]), data)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_create_post(self):
- """
- Creates a post in a topic.
- """
- data = {
- 'text': 'Welcome to this post!'
- }
- topic = self.create_multiple_forums_with_topics(1, 1)
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- post = Post.objects.filter(topic=topic.id).last()
-
- self.assertEqual(response.data.get('text'), data.get('text'))
- self.assertEqual(response.data.get('text'), post.text)
- self.assertEqual(response.data.get('is_useful'), post.is_useful)
- self.assertEqual(response.data.get('author'), post.author.id)
- self.assertEqual(response.data.get('position'), post.position)
-
- def test_failure_post_in_a_forum_we_cannot_read(self):
- """
- Tries to create a post in a private topic with a normal user.
- """
- print('--------------------------- test rate--------------')
- profile = StaffProfileFactory()
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, profile)
- data = {
- 'text': 'Welcome to this post!'
- }
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.pk]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_post_in_a_private_forum(self):
- """
- Post in a private topic with a user that has access right.
- """
-
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, self.staff)
- data = {
- 'text': 'Welcome to this post!'
- }
- response = self.client_authenticated_staff.post(reverse('api:forum:list-post', args=[topic.pk,]), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- post = Post.objects.filter(topic=topic.id).last()
- self.assertEqual(response.data.get('text'), data.get('text'))
- self.assertEqual(response.data.get('text'), post.text)
- self.assertEqual(response.data.get('is_useful'), post.is_useful)
- self.assertEqual(response.data.get('author'), post.author.id)
- self.assertEqual(response.data.get('position'), post.position)
-
- def test_new_post_user_with_restrictions(self):
- """
- Try to post a new post with an user that has some restrictions .
- """
-
- # Banned
- profile = ProfileFactory()
- profile.can_read = False
- profile.can_write = False
- profile.save()
- topic = self.create_multiple_forums_with_topics(1, 1)
- data = {
- 'text': 'I head that Flask is the best framework ever, is that true ?'
- }
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.post(reverse('api:forum:list-post', args=[topic.id,]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Read only
- profile = ProfileFactory()
- profile.can_write = False
- profile.save()
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.post(reverse('api:forum:list-post', args=[topic.id,]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_detail_post(self):
- """
- Gets all information about a post.
- """
-
- topic, posts = self.create_topic_with_post()
- post = posts[0]
- response = self.client.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(post.id, response.data.get('id'))
- self.assertIsNotNone(response.data.get('text'))
- self.assertIsNotNone(response.data.get('text_html'))
- self.assertIsNotNone(response.data.get('pubdate'))
- self.assertIsNone(response.data.get('update'))
- print('test_detail_post')
- print(response.data)
- self.assertEqual(post.position, response.data.get('position'))
- self.assertEqual(topic.author.id, response.data.get('author'))
-
- def test_detail_of_a_private_post_not_present(self):
- """
- Gets an error 404 when the post isn't present in the database.
- """
- topic = self.create_multiple_forums_with_topics(1, 1)
- response = self.client.get(reverse('api:forum:detail-post', args=[topic.id, 666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_detail_of_private_post(self):
- """
- Tries to get all the data about a post in a private topic (and forum) with different users.
- """
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, self.profile)
- post = Post.objects.filter(topic=topic.id).first()
-
- # Anonymous
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # User
- response = self.client_authenticated.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff user
- response = self.client_authenticated_staff.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
- def test_list_of_member_posts_empty(self):
- """
- Gets empty list of posts that that a specified member created.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_member_posts(self):
- """
- Gets list of a member posts not empty in the database.
- """
- self.create_multiple_forums_with_topics(10, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 10)
- self.assertEqual(len(response.data.get('results')), 10)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_staff_posts(self):
- """
- Gets list of a staff posts.
- """
-
- profile = StaffProfileFactory()
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, profile)
-
- # Anonymous user cannot see staff private post.
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:list-memberpost', args=[profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
-
- # Same for normal user
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
-
- response = self.client_authenticated_staff.get(reverse('api:forum:list-memberpost', args=[profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 1)
-
- def test_list_of_member_posts_with_several_pages(self):
- """
- Gets list of a member topics with several pages in the database.
- """
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 1)
-
- def test_list_of_member_posts_for_a_page_given(self):
- """
- Gets list of a member topics with several pages and gets a page different that the first one.
- """
-
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id])+ '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 11)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_member_post_for_a_wrong_page_given(self):
- """
- Gets an error when the member posts asks a wrong page.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_member_posts_with_a_custom_page_size(self):
- """
- Gets list of user's posts with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- # When a topic is created, a post is also created.
- self.create_topic_with_post((REST_PAGE_SIZE * 2) - 1, self.profile)
-
- page_size = 'page_size'
- response = self.client.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_member_posts_with_a_wrong_custom_page_size(self):
- """
- Gets list of member posts with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- self.create_multiple_forums_with_topics(page_size_value, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), page_size_value)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_list_of_unknow_member_posts(self):
- """
- Gets empty list of posts for a member that does not exists.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_alert_post(self):
- """
- Tries to alert a post in a public forum with different type of users
- """
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- another_profile = ProfileFactory()
- post = PostFactory(topic=topic, author=another_profile.user, position=1)
- data = {
- 'text': 'There is a guy flooding about Flask, con you do something about it ?'
- }
-
- self.client = APIClient()
- response = self.client.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- alerte = Alert.objects.latest('pubdate')
- self.assertEqual(alerte.text, data.get('text'))
- self.assertEqual(alerte.author, self.profile.user)
- self.assertEqual(alerte.comment.id, post.id)
-
-
- def test_alert_post_in_private_forum(self):
- """
- Tries to alert a post in a public forum with different type of users
- """
- profile = StaffProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- post = PostFactory(topic=topic, author=profile.user, position=1)
- data = {
- 'text': 'There is a guy flooding about Flask, con you do something about it ?'
- }
-
- self.client = APIClient()
- response = self.client.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- response = self.client_authenticated_staff.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- alerte = Alert.objects.latest('pubdate')
- self.assertEqual(alerte.text, data.get('text'))
- self.assertEqual(alerte.author, self.client_authenticated_staff)
- self.assertEqual(alerte.comment, post.id)
-
- def test_alert_post_not_found(self):
- """
- Tries to alert a post in a public forum with different type of users
- """
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- post = PostFactory(topic=topic, author=profile.user, position=1)
- data = {
- 'text': 'There is a guy flooding about Flask, con you do something about it ?'
- }
-
- response = self.client_authenticated.post(reverse('api:forum:alert-post', args=[topic.id, 666]), data)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
-# Edite un message d'un sujet fermé user
-# Edite un message d'un sujet fermé staff
-
-# TODO
-
-
- def test_update_post_anonymous(self):
- """
- Tries to update a post with anonymous user.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- self.client = APIClient();
- topic, posts = self.create_topic_with_post()
- response = self.client.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- def test_update_post_other_user(self):
- """
- Tries to update a post with another user that the one who posted on the first time.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- another_profile = ProfileFactory()
- topic, posts = self.create_topic_with_post(REST_PAGE_SIZE, another_profile)
- print('test_update_post_other_user')
- response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_update_post(self):
- """
- Updates a post with user.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- topic, posts = self.create_topic_with_post(1, self.profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('text'), data.get('text'))
-
- def test_update_post_staff(self):
- """
- Update a post with a staff user.
- """
- data = {
- 'text': 'I am Vladimir Lupin, I do want I want.'
- }
- topic, posts = self.create_topic_with_post()
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('text'), data.get('text'))
-
- def test_update_unknow_post(self):
- """
- Tries to update post that does not exist.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[666, 42]), data)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_update_post_in_private_topic(self):
- """
- Tries to update a post in a private forum (and topic) with anonymous user.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- self.client = APIClient();
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, self.staff)
- post = PostFactory(topic=topic, author=self.staff.user, position=1)
-
- # With anonymous
- response = self.client.put(reverse('api:forum:detail-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # With user
- response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # With staff (member of private forum)
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(data.get('text'), response.data.get('text'))
-
-def create_oauth2_client(user):
- client = Application.objects.create(user=user,
- client_type=Application.CLIENT_CONFIDENTIAL,
- authorization_grant_type=Application.GRANT_PASSWORD)
- client.save()
- return client
-
-
-def authenticate_client(client, client_auth, username, password):
- client.post('/oauth2/token/', {
- 'client_id': client_auth.client_id,
- 'client_secret': client_auth.client_secret,
- 'username': username,
- 'password': password,
- 'grant_type': 'password'
- })
- access_token = AccessToken.objects.get(user__username=username)
- client.credentials(HTTP_AUTHORIZATION='Bearer {}'.format(access_token))
-
-# TODO
-# Reorganiser le code de test en differentes classes, reordonner les tests
-# Gérer le champ update (date) lors de l'edit
-# Gérer l'antispam
-# identifer quand masquer les messages (message modéré ou masqué par son auteur)
-# empecher les ls et les ban de faire des alertes
-# Gestion de tags (post/edit/details)
-# Tests qui ne passent pas
-# Style / PEP8
-
-# TESTS MANQUANTS
-# Vérifier que l'on affiche pas le text hidden ou l'adresse ip
-# Créer un topic avec des tags (ajouter le test), éditer les tags
-# Tester le cas ou un user veux vider le contenu de son message
-# Ajouter le cas ou le staff ou un user masque son message, mais un autre user ne peut pas le faire
-# Poste dans un sujet fermé (3 roles)
diff --git a/zds/forum/api/urls.py b/zds/forum/api/urls.py
deleted file mode 100644
index a30fdb4665..0000000000
--- a/zds/forum/api/urls.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.conf.urls import url
-
-from .views import PostKarmaView, ForumListAPI, ForumDetailAPI, PostListAPI, TopicListAPI, TopicDetailAPI, UserTopicListAPI, MemberPostListAPI, UserPostListAPI, PostDetailAPI, PostAlertAPI
-urlpatterns = [
- url(r'^$', ForumListAPI.as_view(), name='list'),
- url(r'^(?P[0-9]+)/?$', ForumDetailAPI.as_view(), name='detail'),
- url(r'^sujets/?$', TopicListAPI.as_view(), name='list-topic'),
- url(r'^membre/sujets/?$', UserTopicListAPI.as_view(), name='list-usertopic'),
- url(r'^message/(?P\d+)/karma/?$', PostKarmaView.as_view(), name='post-karma'),
- url(r'^sujets/(?P[0-9]+)/messages?$', PostListAPI.as_view(), name='list-post'),
- url(r'^sujets/(?P[0-9]+)/?$', TopicDetailAPI.as_view(), name='detail-topic'),
- url(r'^membres/(?P[0-9]+)/messages/?$', MemberPostListAPI.as_view(), name='list-memberpost'),
- url(r'^membres/messages/?$', UserPostListAPI.as_view(), name='list-userpost'),
- url(r'^sujets/(?P[0-9]+)/messages/(?P[0-9]+)/?$', PostDetailAPI.as_view(), name='detail-post'),
- url(r'^sujets/(?P[0-9]+)/messages/(?P[0-9]+)/alert/?$', PostAlertAPI.as_view(), name='alert-post')
-]
diff --git a/zds/forum/api/views.py b/zds/forum/api/views.py
deleted file mode 100644
index 378dbe256b..0000000000
--- a/zds/forum/api/views.py
+++ /dev/null
@@ -1,647 +0,0 @@
-# coding: utf-8
-
-from zds.member.api.permissions import CanReadTopic, CanReadPost, CanReadForum, CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, IsOwnerOrReadOnly, IsStaffUser
-from zds.utils.api.views import KarmaView
-from zds.forum.models import Post, Forum, Topic, Category
-import datetime
-from django.core.cache import cache
-from django.db.models.signals import post_save, post_delete
-from django.http import Http404
-from rest_framework import filters
-from rest_framework.generics import ListAPIView, ListCreateAPIView, RetrieveUpdateAPIView, RetrieveAPIView, CreateAPIView
-from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor
-from rest_framework_extensions.cache.decorators import cache_response
-from rest_framework_extensions.etag.decorators import etag
-from rest_framework_extensions.key_constructor import bits
-from rest_framework.response import Response
-from rest_framework import status
-from rest_framework.permissions import IsAuthenticatedOrReadOnly, AllowAny, IsAuthenticated
-from dry_rest_permissions.generics import DRYPermissions
-from zds.api.bits import DJRF3xPaginationKeyBit, UpdatedAtKeyBit
-from zds.utils import slugify
-from zds.forum.api.serializer import ForumSerializer, TopicSerializer, TopicCreateSerializer, TopicUpdateSerializer, TopicUpdateStaffSerializer, PostSerializer, PostCreateSerializer, PostUpdateSerializer, AlertSerializer
-from zds.forum.api.permissions import IsStaffUser, IsOwnerOrIsStaff, CanWriteInForum, CanWriteInTopic
-from zds.member.models import User
-from itertools import chain
-
-class PostKarmaView(KarmaView):
- queryset = Post.objects.all()
- permission_classes = (IsAuthenticatedOrReadOnly, CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, CanReadPost)
-
-
-class PagingSearchListKeyConstructor(DefaultKeyConstructor):
- pagination = DJRF3xPaginationKeyBit()
- list_sql_query = bits.ListSqlQueryKeyBit()
- unique_view_id = bits.UniqueViewIdKeyBit()
- user = bits.UserKeyBit()
- updated_at = UpdatedAtKeyBit('api_updated_forum')
-
-
-class DetailKeyConstructor(DefaultKeyConstructor):
- format = bits.FormatKeyBit()
- language = bits.LanguageKeyBit()
- retrieve_sql_query = bits.RetrieveSqlQueryKeyBit()
- unique_view_id = bits.UniqueViewIdKeyBit()
- user = bits.UserKeyBit()
- updated_at = UpdatedAtKeyBit('api_updated_forum')
-
-
-def change_api_forum_updated_at(sender=None, instance=None, *args, **kwargs):
- cache.set('forum_updated_tag', datetime.datetime.utcnow())
-
-
-post_save.connect(receiver=change_api_forum_updated_at, sender=Forum)
-post_delete.connect(receiver=change_api_forum_updated_at, sender=Forum)
-
-
-class ForumListAPI(ListCreateAPIView):
- """
- Profile resource to list all forum.
- """
- serializer_class = ForumSerializer
- list_key_func = PagingSearchListKeyConstructor()
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all forum in the system.
- ---
-
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
-
- def get_queryset(self):
- public_forums = Forum.objects.filter(group__isnull=True).order_by('position_in_category')
- private_forums = Forum.objects.filter(group__in=self.request.user.groups.all()).order_by('position_in_category')
- return public_forums | private_forums
-
- def get_permissions(self):
- permission_classes = [CanReadForum] # TODO style plus joli ?
- return [permission() for permission in permission_classes]
-
-
-class ForumDetailAPI(RetrieveAPIView):
- """
- Profile resource to display details of a forum.
- ---
- """
-
- queryset = Forum.objects.all()
- obj_key_func = DetailKeyConstructor()
- serializer_class = ForumSerializer
-
- @etag(obj_key_func)
- @cache_response(key_func=obj_key_func)
- def get(self, request, *args, **kwargs):
- """
- Gets a forum given by its identifier.
- ---
- responseMessages:
- - code: 404
- message: Not Found
- """
- forum = self.get_object()
-
- return self.retrieve(request, *args, **kwargs)
-
- def get_serializer_class(self):
- return ForumSerializer
-
- def get_permissions(self):
- permission_classes = [CanReadForum] # TODO style plus joli ?
- return [permission() for permission in permission_classes]
-
-
-class TopicListAPI(ListCreateAPIView):
- """
- Profile resource to list all topics (GET) or to create a topic (POST)
- """
- queryset = Topic.objects.all()
- filter_backends = (filters.DjangoFilterBackend,)
- filter_fields = ('forum', 'author', 'tags__title')
- list_key_func = PagingSearchListKeyConstructor()
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all topic in a forum.
- ---
-
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- - name: forum
- description: Filters by forum id.
- required: false
- - name: author
- description: Filters by author id.
- required: false
- - name: tags__title
- description: Filters by tag name.
- required: false
- responseMessages:
- - code: 404
- message: Not Found
- """
-
- return self.list(request, *args, **kwargs)
-
- def post(self, request, *args, **kwargs):
- """
- Creates a new topic.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: title
- description: Title of the Topic.
- required: true
- paramType: form
- - name: subtitle
- description: Subtitle of the Topic.
- required: false
- paramType: form
- - name: forum
- description: Identifier of the forum in which the Topic should be posted.
- required: false
- paramType: form
- - name: text
- description: Content of the first post in markdown.
- required: true
- paramType: form
- - name: tags
- description: To add a tag, specify its taf identifier. Specify this parameter
- several times to add several tags.
- required: false
- paramType: form
- responseMessages:
- - code: 400
- message: Bad Request
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Forbidden
- """
- author = request.user.id
- serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
- serializer.is_valid(raise_exception=True)
- serializer.save(author_id=author)
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
-
- def get_serializer_class(self):
- if self.request.method == 'GET':
- return TopicSerializer
- elif self.request.method == 'POST':
- return TopicCreateSerializer
-
- def get_permissions(self):
- permission_classes = [CanReadForum]
- if self.request.method == 'POST':
- print('requete post')
-
- #forum = Forum.objects.get(id=self.request.data.get('forum'))
- #self.check_object_permissions(self.request, forum)
- #permission_classes.append(CanReadAndWriteNowOrReadOnly)
- permission_classes.append(CanWriteInForum)
- permission_classes.append(CanReadAndWriteNowOrReadOnly)
- return [permission() for permission in permission_classes]
-
-
-class UserTopicListAPI(ListAPIView):
- """
- Profile resource to list all topics from current user
- """
-
- serializer_class = TopicSerializer
- filter_backends = (filters.DjangoFilterBackend,)
- filter_fields = ('forum', 'tags__title')
- list_key_func = PagingSearchListKeyConstructor()
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all topic from current user.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 401
- message: Not Authenticated
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
-
- def get_queryset(self):
- topics = Topic.objects.filter(author=self.request.user)
- return topics
-
- def get_permissions(self):
- permission_classes = [AllowAny, IsAuthenticated]
- return [permission() for permission in permission_classes]
-
-
-class TopicDetailAPI(RetrieveUpdateAPIView):
- """
- Profile resource to display and update details of a given topic
- """
- queryset = Topic.objects.all()
- obj_key_func = DetailKeyConstructor()
-
- @etag(obj_key_func)
- @cache_response(key_func=obj_key_func)
- def get(self, request, *args, **kwargs):
- """
- Gets a topic given by its identifier.
- ---
- responseMessages:
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Forbidden
- - code: 404
- message: Not Found
- """
- topic = self.get_object()
-
- return self.retrieve(request, *args, **kwargs)
-
- def put(self, request, *args, **kwargs):
- """
- Updates a topic. Said post must be owned by the authenticated member.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: title
- description: Title of the Topic.
- required: false
- paramType: form
- - name: subtitle
- description: Subtitle of the Topic.
- required: false
- paramType: form
- - name: text
- description: Content of the first post in markdown.
- required: false
- paramType: form
- - name: tags
- description: To add a tag, specify its taf identifier. Specify this parameter
- several times to add several tags.
- required: false
- paramType: form
- responseMessages:
- - code: 400
- message: Bad Request if you specify a bad identifier
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- return self.update(request, *args, **kwargs)
-
- def get_serializer_class(self):
- if self.request.method == 'GET':
- return TopicSerializer
- elif self.request.method == 'PUT':
- if self.request.user.has_perm("forum.change_topic"):
- return TopicUpdateStaffSerializer
- else:
- return TopicUpdateSerializer
-
- def get_permissions(self):
- permission_classes = []
- if self.request.method == 'GET':
- permission_classes.append(CanReadTopic)
- elif self.request.method == 'PUT':
- print(self.request.user)
- permission_classes.append(IsOwnerOrIsStaff)
- permission_classes.append(CanReadTopic)
- return [permission() for permission in permission_classes]
-
-
-class PostListAPI(ListCreateAPIView):
- """
- Profile resource to list all messages in a topic
- """
- list_key_func = PagingSearchListKeyConstructor()
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all posts in a topic
- ---
-
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
- # TODO si message cache ? Le cacher dans l'API
-
- def post(self, request, *args, **kwargs):
- """
- Creates a new post in a topic.
- ---
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: text
- description: Content of the post in markdown.
- required: true
- paramType: form
- - name: text
- description: Content of the first post in markdown.
- required: true
- responseMessages:
- - code: 400
- message: Bad Request
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- author = request.user.id
- topic = self.kwargs.get('pk')
-
- serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
- serializer.is_valid(raise_exception=True)
- serializer.save(position=0, author_id=author, topic_id=topic)
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
-
- def get_serializer_class(self):
- if self.request.method == 'GET':
- return PostSerializer
- elif self.request.method == 'POST':
- return PostCreateSerializer
-
- def get_queryset(self):
- if self.request.method == 'GET':
- posts = Post.objects.filter(topic=self.kwargs.get('pk'))
- if posts.count() == 0:
- raise Http404("Topic with pk {} was not found".format(self.kwargs.get('pk')))
- return posts
-
- def get_current_user(self):
- return self.request.user.profile
-
- def get_permissions(self):
- permission_classes = [CanReadPost]
- if self.request.method == 'POST':
- permission_classes.append(CanReadAndWriteNowOrReadOnly)
- permission_classes.append(CanWriteInTopic)
-
- return [permission() for permission in permission_classes]
-
-
-class MemberPostListAPI(ListAPIView):
- """
- Profile resource to list all posts from a member
- """
- list_key_func = PagingSearchListKeyConstructor()
- serializer_class = PostSerializer
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all posts from a member
- ---
-
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
- # TODO fonctionne mais error xml sur certains post http://zds-anto59290.c9users.io/api/forums/membres/3/sujets
-
- def get_queryset(self):
- if self.request.method == 'GET':
- try:
- author = User.objects.get(pk = self.kwargs.get('pk'))
- except User.DoesNotExist:
- raise Http404("User with pk {} was not found".format(self.kwargs.get('pk')))
-
- # Gets every post of author visible by current user
- posts = Post.objects.get_all_messages_of_a_user(self.request.user, author)
- return posts
-
-
-class UserPostListAPI(ListAPIView):
- """
- Profile resource to list all message from current user
- """
-
- list_key_func = PagingSearchListKeyConstructor()
- serializer_class = PostSerializer
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all posts from a current user.
- ---
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
-
- def get_queryset(self):
- if self.request.method == 'GET':
- posts = Post.objects.filter(author=self.request.user)
- return posts
-
-
-class PostDetailAPI(RetrieveUpdateAPIView):
- """
- Profile resource to display details of given post
- """
-
- queryset = Post.objects.all()
- obj_key_func = DetailKeyConstructor()
-
- @etag(obj_key_func)
- @cache_response(key_func=obj_key_func)
- def get(self, request, *args, **kwargs):
- """
- Gets a post given by its identifier.
- ---
- responseMessages:
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- post = self.get_object()
-
- return self.retrieve(request, *args, **kwargs)
-
- def put(self, request, *args, **kwargs):
- """
- Updates a post. Said post must be owned by the authenticated member.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: text
- description: Content of the post in markdown.
- required: true
- paramType: form
- responseMessages:
- - code: 400
- message: Bad Request if you specify a bad identifier
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- return self.update(request, *args, **kwargs)
-
- def get_serializer_class(self):
- if self.request.method == 'GET':
- return PostSerializer
- elif self.request.method == 'PUT':
- return PostUpdateSerializer
-
- def get_permissions(self):
- permission_classes = [AllowAny, CanReadPost]
- if self.request.method == 'PUT':
- permission_classes.append(IsOwnerOrIsStaff)
- permission_classes.append(CanReadAndWriteNowOrReadOnly)
- return [permission() for permission in permission_classes]
-
-
-class PostAlertAPI(CreateAPIView):
- """
- Alert a topic post to the staff.
- """
-
- def post(self, request, *args, **kwargs):
- """
- Alert a topic post to the staff.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: text
- description: Content of the alert in markdown.
- required: true
- paramType: form
- responseMessages:
- - code: 400
- message: Bad Request
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- author = request.user
- try:
- post = Post.objects.get(id = self.kwargs.get('pk'))
- except Post.DoesNotExist:
- raise Http404("Post with pk {} was not found".format(self.kwargs.get('pk')))
-
- serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
- serializer.is_valid(raise_exception=True)
- serializer.save(comment=post, pubdate = datetime.datetime.now(), author = author)
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
-
- def get_permissions(self):
- permission_classes = [CanReadPost, IsAuthenticated]
- return [permission() for permission in permission_classes]
-
- def get_serializer_class(self):
- return AlertSerializer
diff --git a/zds/utils/validators.py b/zds/utils/validators.py
deleted file mode 100644
index 540aaae472..0000000000
--- a/zds/utils/validators.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# -*- coding: utf-8 -*-
-import logging
-
-from django.utils.translation import ugettext_lazy as _
-from zds.api.validators import Validator
-from zds.utils.models import Tag
-from zds.utils.misc import contains_utf8mb4
-
-
-class TitleValidator(Validator):
- """
- Validates title field of a Comment.
- """
-
- def validate_title(self, value):
- """
- Checks about title.
-
- :param value: title value
- :return: title value
- """
- msg = None
- if value:
- if value.strip() == '':
- msg = _(u'Le champ titre ne peut être vide.')
- if msg is not None:
- self.throw_error('title', msg)
- return value
-
-
-class TextValidator(Validator):
- """
- Validates text field of a MP.
- """
-
- def validate_text(self, value):
- """
- Checks about text.
-
- :param value: text value
- :return: text value
- """
- msg = None
- if value:
- if value.strip() == '':
- msg = _(u'Le champ text ne peut être vide.')
- if msg is not None:
- self.throw_error('text', msg)
- return value
-
-
-class TagValidator(object):
- """
- validate tags
- """
- def __init__(self):
- self.__errors = []
- self.logger = logging.getLogger("zds.utils.forms")
- self.__clean = []
-
- def validate_raw_string(self, raw_string):
- """
- validate a string composed as ``tag1,tag2``.
-
- :param raw_string: the string to be validate. If ``None`` this is considered as a empty str
- :type raw_string: basestring
- :return: ``True`` if ``raw_string`` is fully valid, ``False`` if at least one error appears. See ``self.errors``
- to get all internationalized error.
- """
- if raw_string is None or not isinstance(raw_string, basestring):
- return self.validate_string_list([])
- return self.validate_string_list(raw_string.split(","))
-
- def validate_length(self, tag):
- """
- Check the length is in correct range. See ``Tag.label`` max length to have the true upper bound.
-
- :param tag: the tag lavel to validate
- :return: ``True`` if length is valid
- """
- if len(tag) > Tag._meta.get_field("title").max_length:
- self.errors.append(_(u"Le tag {} est trop long (maximum {} caractères)".format(
- tag, Tag._meta.get_field("title").max_length)))
- self.logger.debug("%s est trop long expected=%d got=%d", tag,
- Tag._meta.get_field("title").max_length, len(tag))
- return False
- return True
-
- def validate_string_list(self, string_list):
- """
- Same as ``validate_raw_string`` but with a list of tag labels.
-
- :param string_list:
- :return: ``True`` if ``v`` is fully valid, ``False`` if at least one error appears. See ``self.errors``
- to get all internationalized error.
- """
- self.__clean = list(filter(self.validate_length, string_list))
- self.__clean = list(filter(self.validate_utf8mb4, self.__clean))
- return len(string_list) == len(self.__clean)
-
- def validate_utf8mb4(self, tag):
- """
- Checks the tag does not contain utf8mb4 chars.
-
- :param tag:
- :return: ``True`` if no utf8mb4 string is found
- """
- if contains_utf8mb4(tag):
- self.errors.append(_(u"Le tag {} contient des caractères utf8mb4").format(tag))
- self.logger.warn("%s contains utf8mb4 char", tag)
- return False
- return True
-
- @property
- def errors(self):
- return self.__errors
From aa4afcc25c41e92784f4af5fe8d016b49a569bbc Mon Sep 17 00:00:00 2001
From: Antonin
Date: Wed, 10 May 2017 20:04:36 +0000
Subject: [PATCH 50/78] Fix rebase
---
zds/forum/api/tests.py | 1684 ++++++++++++++++++++++++++++++++++++++++
zds/forum/api/urls.py | 16 +
zds/forum/api/views.py | 647 +++++++++++++++
3 files changed, 2347 insertions(+)
create mode 100644 zds/forum/api/tests.py
create mode 100644 zds/forum/api/urls.py
create mode 100644 zds/forum/api/views.py
diff --git a/zds/forum/api/tests.py b/zds/forum/api/tests.py
new file mode 100644
index 0000000000..c8cdf3d6cd
--- /dev/null
+++ b/zds/forum/api/tests.py
@@ -0,0 +1,1684 @@
+# coding: utf-8
+
+from django.conf import settings
+from django.core.cache import caches
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import Group
+from django.db import transaction
+from rest_framework import status
+from rest_framework.test import APIClient
+from rest_framework.test import APITestCase
+from oauth2_provider.models import Application, AccessToken
+from rest_framework_extensions.settings import extensions_api_settings
+from zds.api.pagination import REST_PAGE_SIZE, REST_MAX_PAGE_SIZE, REST_PAGE_SIZE_QUERY_PARAM
+from zds.member.factories import ProfileFactory, StaffProfileFactory
+from zds.forum.models import Forum, Topic, Post
+from zds.forum.factories import PostFactory
+from zds.forum.tests.tests_views import create_category, add_topic_in_a_forum
+from zds.utils.models import CommentVote, Alert
+
+
+class ForumPostKarmaAPITest(APITestCase):
+ def setUp(self):
+
+ self.client = APIClient()
+ self.profile = ProfileFactory()
+
+ client_oauth2 = create_oauth2_client(self.profile.user)
+ self.client_authenticated = APIClient()
+ authenticate_client(self.client_authenticated, client_oauth2, self.profile.user.username, 'hostel77')
+
+ caches[extensions_api_settings.DEFAULT_USE_CACHE].clear()
+
+ def test_failure_post_karma_with_client_unauthenticated(self):
+ profile = ProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ post = PostFactory(topic=topic, author=profile.user, position=2)
+
+ response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)))
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_failure_post_karma_with_sanctioned_user(self):
+ profile = ProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ another_profile = ProfileFactory()
+ post = PostFactory(topic=topic, author=another_profile.user, position=2)
+
+ profile = ProfileFactory()
+ profile.can_read = False
+ profile.can_write = False
+ profile.save()
+
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+ response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)))
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_failure_post_karma_with_a_message_not_found(self):
+ response = self.client.get(reverse('api:forum:post-karma', args=(99999,)))
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_failure_post_karma_of_a_forum_we_cannot_read(self):
+ group = Group.objects.create(name='DummyGroup_1')
+
+ profile = ProfileFactory()
+ category, forum = create_category(group)
+ topic = add_topic_in_a_forum(forum, profile)
+
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+ response = self.client.put(reverse('api:forum:post-karma', args=(topic.last_message.pk,)), {'vote': 'like'})
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_success_post_karma_like(self):
+ profile = ProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ another_profile = ProfileFactory()
+ post = PostFactory(topic=topic, author=another_profile.user, position=2)
+
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+ response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)), {'vote': 'like'})
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertTrue(CommentVote.objects.filter(user=profile.user, comment=post, positive=True).exists())
+
+ def test_success_post_karma_dislike(self):
+ profile = ProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ another_profile = ProfileFactory()
+ post = PostFactory(topic=topic, author=another_profile.user, position=2)
+
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+ response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)), {'vote': 'dislike'})
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertTrue(CommentVote.objects.filter(user=profile.user, comment=post, positive=False).exists())
+
+ def test_success_post_karma_neutral(self):
+ profile = ProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ another_profile = ProfileFactory()
+ post = PostFactory(topic=topic, author=another_profile.user, position=2)
+
+ vote = CommentVote(user=profile.user, comment=post, positive=True)
+ vote.save()
+
+ self.assertTrue(CommentVote.objects.filter(pk=vote.pk).exists())
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+ response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)), {'vote': 'neutral'})
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertFalse(CommentVote.objects.filter(pk=vote.pk).exists())
+
+ def test_success_post_karma_like_already_disliked(self):
+ profile = ProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ another_profile = ProfileFactory()
+ post = PostFactory(topic=topic, author=another_profile.user, position=2)
+
+ vote = CommentVote(user=profile.user, comment=post, positive=False)
+ vote.save()
+
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+ response = self.client.put(reverse('api:forum:post-karma', args=(post.pk,)), {'vote': 'like'})
+ vote.refresh_from_db()
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertTrue(vote.positive)
+
+ def test_get_post_voters(self):
+ profile = ProfileFactory()
+ profile2 = ProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ another_profile = ProfileFactory()
+
+ upvoted_answer = PostFactory(topic=topic, author=another_profile.user, position=2)
+ upvoted_answer.like += 2
+ upvoted_answer.save()
+ CommentVote.objects.create(user=profile.user, comment=upvoted_answer, positive=True)
+
+ downvoted_answer = PostFactory(topic=topic, author=another_profile.user, position=3)
+ downvoted_answer.dislike += 2
+ downvoted_answer.save()
+ anon_limit = CommentVote.objects.create(user=profile.user, comment=downvoted_answer, positive=False)
+
+ CommentVote.objects.create(user=profile2.user, comment=upvoted_answer, positive=True)
+ CommentVote.objects.create(user=profile2.user, comment=downvoted_answer, positive=False)
+
+ equal_answer = PostFactory(topic=topic, author=another_profile.user, position=4)
+ equal_answer.like += 1
+ equal_answer.dislike += 1
+ equal_answer.save()
+ CommentVote.objects.create(user=profile.user, comment=equal_answer, positive=True)
+ CommentVote.objects.create(user=profile2.user, comment=equal_answer, positive=False)
+
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+
+ # on first message we should see 2 likes and 0 anonymous
+ response = self.client.get(reverse('api:forum:post-karma', args=[upvoted_answer.pk]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(2, len(response.data['like']['users']))
+ self.assertEqual(0, len(response.data['dislike']['users']))
+ self.assertEqual(2, response.data['like']['count'])
+ self.assertEqual(0, response.data['dislike']['count'])
+
+ # on second message we should see 2 dislikes and 0 anonymous
+ response = self.client.get(reverse('api:forum:post-karma', args=[downvoted_answer.pk]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(0, len(response.data['like']['users']))
+ self.assertEqual(2, len(response.data['dislike']['users']))
+ self.assertEqual(0, response.data['like']['count'])
+ self.assertEqual(2, response.data['dislike']['count'])
+
+ # on third message we should see 1 like and 1 dislike and 0 anonymous
+ response = self.client.get(reverse('api:forum:post-karma', args=[equal_answer.pk]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(1, len(response.data['like']['users']))
+ self.assertEqual(1, len(response.data['dislike']['users']))
+ self.assertEqual(1, response.data['like']['count'])
+ self.assertEqual(1, response.data['dislike']['count'])
+
+ # Now we change the settings to keep anonymous the first [dis]like
+ settings.VOTES_ID_LIMIT = anon_limit.pk
+ # and we run the same tests
+ # on first message we should see 1 like and 1 anonymous
+ response = self.client.get(reverse('api:forum:post-karma', args=[upvoted_answer.pk]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(1, len(response.data['like']['users']))
+ self.assertEqual(0, len(response.data['dislike']['users']))
+ self.assertEqual(2, response.data['like']['count'])
+ self.assertEqual(0, response.data['dislike']['count'])
+
+ # on second message we should see 1 dislikes and 1 anonymous
+ response = self.client.get(reverse('api:forum:post-karma', args=[downvoted_answer.pk]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(0, len(response.data['like']['users']))
+ self.assertEqual(1, len(response.data['dislike']['users']))
+ self.assertEqual(0, response.data['like']['count'])
+ self.assertEqual(2, response.data['dislike']['count'])
+
+ # on third message we should see 1 like and 1 dislike and 0 anonymous
+ response = self.client.get(reverse('api:forum:post-karma', args=[equal_answer.pk]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(1, len(response.data['like']['users']))
+ self.assertEqual(1, len(response.data['dislike']['users']))
+ self.assertEqual(1, response.data['like']['count'])
+ self.assertEqual(1, response.data['dislike']['count'])
+
+
+class ForumAPITest(APITestCase):
+ def setUp(self):
+ self.client = APIClient()
+
+ self.profile = ProfileFactory()
+ client_oauth2 = create_oauth2_client(self.profile.user)
+ self.client_authenticated = APIClient()
+ authenticate_client(self.client_authenticated, client_oauth2, self.profile.user.username, 'hostel77')
+
+ self.staff = StaffProfileFactory()
+ client_oauth2 = create_oauth2_client(self.staff.user)
+ self.client_authenticated_staff = APIClient()
+ authenticate_client(self.client_authenticated_staff, client_oauth2, self.staff.user.username, 'hostel77')
+
+ self.group_staff = Group.objects.filter(name="staff").first()
+
+ caches[extensions_api_settings.DEFAULT_USE_CACHE].clear()
+
+ def create_multiple_forums(self, number_of_forum=REST_PAGE_SIZE):
+ for forum in xrange(0, number_of_forum):
+ category, forum = create_category()
+
+ def create_multiple_forums_with_topics(self, number_of_forum=REST_PAGE_SIZE, number_of_topic=REST_PAGE_SIZE, profile=None):
+ if profile is None:
+ profile = ProfileFactory()
+ for forum in xrange(0, number_of_forum):
+ category, forum = create_category()
+ for topic in xrange(0, number_of_topic):
+ new_topic = add_topic_in_a_forum(forum, profile)
+ if number_of_forum == 1 and number_of_topic == 1:
+ return new_topic
+
+ def create_topic_with_post(self, number_of_post=REST_PAGE_SIZE, profile=None):
+ if profile is None:
+ profile = ProfileFactory()
+
+ category, forum = create_category()
+ new_topic = add_topic_in_a_forum(forum, profile)
+ posts = []
+
+ for post in xrange(0, number_of_post):
+ posts.append(PostFactory(topic=new_topic, author=profile.user, position=2))
+
+ return new_topic, posts
+
+ def test_list_of_forums_empty(self):
+ """
+ Gets empty list of forums in the database.
+ """
+ response = self.client.get(reverse('api:forum:list'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+ self.assertEqual(response.data.get('results'), [])
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_forums(self):
+ """
+ Gets list of forums not empty in the database.
+ """
+ self.create_multiple_forums()
+
+ response = self.client.get(reverse('api:forum:list'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_forums_private(self):
+ """
+ Gets list of private forums not empty in the database (only for staff).
+ """
+ category, forum = create_category(self.group_staff)
+
+ self.client = APIClient()
+ response = self.client.get(reverse('api:forum:list'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+
+ response = self.client_authenticated.get(reverse('api:forum:list'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+
+ response = self.client_authenticated_staff.get(reverse('api:forum:list'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 1) # TODO nombre a affiner en fonction de la realite
+
+
+ def test_list_of_forums_with_several_pages(self):
+ """
+ Gets list of forums with several pages in the database.
+ """
+ self.create_multiple_forums(REST_PAGE_SIZE + 1)
+
+ response = self.client.get(reverse('api:forum:list'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+
+ response = self.client.get(reverse('api:forum:list') + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), 1)
+
+ def test_list_of_forums_for_a_page_given(self):
+ """
+ Gets list of forums with several pages and gets a page different that the first one.
+ """
+ self.create_multiple_forums(REST_PAGE_SIZE + 1)
+
+ response = self.client.get(reverse('api:forum:list') + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 11)
+ self.assertEqual(len(response.data.get('results')), 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+
+ def test_list_of_forums_for_a_wrong_page_given(self):
+ """
+ Gets an error when the forums asks a wrong page.
+ """
+ response = self.client.get(reverse('api:forum:list') + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_list_of_forums_with_a_custom_page_size(self):
+ """
+ Gets list of forums with a custom page size. DRF allows to specify a custom
+ size for the pagination.
+ """
+ self.create_multiple_forums(REST_PAGE_SIZE * 2)
+
+ page_size = 'page_size'
+ response = self.client.get(reverse('api:forum:list') + '?{}=20'.format(page_size))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 20)
+ self.assertEqual(len(response.data.get('results')), 20)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
+
+ def test_list_of_forums_with_a_wrong_custom_page_size(self):
+ """
+ Gets list of forums with a custom page size but not good according to the
+ value in settings.
+ """
+ page_size_value = REST_MAX_PAGE_SIZE + 1
+ self.create_multiple_forums(page_size_value)
+
+ response = self.client.get(reverse('api:forum:list') + '?page_size={}'.format(page_size_value))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), page_size_value)
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
+
+ def test_details_forum(self):
+ """
+ Tries to get the details of a forum.
+ """
+
+ category, forum = create_category()
+ response = self.client.get(reverse('api:forum:detail', args=[forum.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('id'), forum.id)
+ self.assertEqual(response.data.get('title'), forum.title)
+ self.assertEqual(response.data.get('subtitle'), forum.subtitle)
+ self.assertEqual(response.data.get('slug'), forum.slug)
+ self.assertEqual(response.data.get('category'), forum.category.id)
+ self.assertEqual(response.data.get('position_in_category'), forum.position_in_category)
+
+ print('-------')
+ print(type(response.data.get('group')))
+ print(type(list(forum.group.all())))
+ print('-------')
+ self.assertEqual(response.data.get('group'), list(forum.group.all()))
+
+
+ def test_details_forum_private(self):
+ """
+ Tries to get the details of a private forum with different users.
+ """
+ category, forum = create_category(self.group_staff)
+
+ self.client = APIClient()
+ response = self.client.get(reverse('api:forum:detail', args=[forum.id]))
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ response = self.client_authenticated.get(reverse('api:forum:detail', args=[forum.id]))
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ response = self.client_authenticated_staff.get(reverse('api:forum:detail', args=[forum.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ def test_details_unknown_forum(self):
+ """
+ Tries to get the details of a forum that does not exists.
+ """
+
+ self.create_multiple_forums(1)
+ response = self.client.get(reverse('api:forum:detail', args=[3]))
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_details_private_forum_user(self):
+ """
+ Tries to get the details of a private forum with a normal user, staff user and anonymous one.
+ """
+ category, forum = create_category(self.group_staff)
+
+ self.client = APIClient()
+ response = self.client.get(reverse('api:forum:detail', args=[forum.id]))
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ response = self.client_authenticated.get(reverse('api:forum:detail', args=[forum.id]))
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ response = self.client_authenticated_staff.get(reverse('api:forum:detail', args=[forum.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+# TODO
+# Récupérer la liste des sujets en filtrant sur le tag (resulat non vide)
+# Récupérer la liste des sujets en filtrant tag, forum, auteur (resulat non vide)
+
+ def test_list_of_topics_empty(self):
+ """
+ Gets empty list of topics in the database.
+ """
+ response = self.client.get(reverse('api:forum:list-topic'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+ self.assertEqual(response.data.get('results'), [])
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_topics(self):
+ """
+ Gets list of topics not empty in the database.
+ """
+ self.create_multiple_forums_with_topics(1)
+ response = self.client.get(reverse('api:forum:list-topic'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_topics_with_several_pages(self):
+ """
+ Gets list of topics with several pages in the database.
+ """
+ self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE + 1)
+
+ response = self.client.get(reverse('api:forum:list-topic'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+
+ response = self.client.get(reverse('api:forum:list-topic') + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), 1)
+
+ def test_list_of_topics_for_a_page_given(self):
+ """
+ Gets list of topics with several pages and gets a page different that the first one.
+ """
+ self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE + 1)
+
+ response = self.client.get(reverse('api:forum:list-topic') + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 11)
+ self.assertEqual(len(response.data.get('results')), 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+
+ def test_list_of_topics_for_a_wrong_page_given(self):
+ """
+ Gets an error when the topics asks a wrong page.
+ """
+ response = self.client.get(reverse('api:forum:list-topic') + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_list_of_topics_with_a_custom_page_size(self):
+ """
+ Gets list of topics with a custom page size. DRF allows to specify a custom
+ size for the pagination.
+ """
+ self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE * 2)
+
+ page_size = 'page_size'
+ response = self.client.get(reverse('api:forum:list-topic') + '?{}=20'.format(page_size))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 20)
+ self.assertEqual(len(response.data.get('results')), 20)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
+
+ def test_list_of_topics_with_a_wrong_custom_page_size(self):
+ """
+ Gets list of topics with a custom page size but not good according to the
+ value in settings.
+ """
+ page_size_value = REST_MAX_PAGE_SIZE + 1
+ self.create_multiple_forums_with_topics(1, page_size_value)
+
+ response = self.client.get(reverse('api:forum:list-topic') + '?page_size={}'.format(page_size_value))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), page_size_value)
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
+
+ def test_list_of_topics_with_forum_filter_empty(self):
+ """
+ Gets an empty list of topics in a forum.
+ """
+ self.create_multiple_forums_with_topics(1)
+ response = self.client.get(reverse('api:forum:list-topic') + '?forum=3')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+ self.assertEqual(response.data.get('results'), [])
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_topics_with_forum_filter(self):
+ """
+ Gets a list of topics in a forum.
+ """
+ self.create_multiple_forums_with_topics(1)
+ forum = Forum.objects.all().first()
+ response = self.client.get(reverse('api:forum:list-topic') + '?forum=' + str(forum.id))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+
+ def test_list_of_topics_with_author_filter_empty(self):
+ """
+ Gets an empty list of topics created by an user.
+ """
+ self.create_multiple_forums_with_topics(1)
+ response = self.client.get(reverse('api:forum:list-topic') + '?author=6')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+ self.assertEqual(response.data.get('results'), [])
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_topics_with_author_filter(self):
+ """
+ Gets a list of topics created by an user.
+ """
+ self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE, self.profile)
+ response = self.client.get(reverse('api:forum:list-topic') + '?author=' + str(self.profile.user.id))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+
+ def test_list_of_topics_with_tag_filter_empty(self):
+ """
+ Gets an empty list of topics with a specific tag.
+ """
+ self.create_multiple_forums_with_topics(1)
+ response = self.client.get(reverse('api:forum:list-topic') + '?tags__title=ilovezozor')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+ self.assertEqual(response.data.get('results'), [])
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_new_topic_with_user(self):
+ """
+ Post a new topic in a forum with an user.
+ """
+ self.create_multiple_forums()
+ data = {
+ 'title': 'Flask 4 Ever !',
+ 'subtitle': 'Is it the best framework ?',
+ 'text': 'I head that Flask is the best framework ever, is that true ?',
+ 'forum': 1
+ }
+
+ response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ topic = Topic.objects.filter(author=self.profile.user.id).first()
+ self.assertEqual(response.data.get('title'), topic.title)
+ self.assertEqual(response.data.get('subtitle'), topic.subtitle)
+ self.assertEqual(data.get('text'), topic.last_message.text)
+ self.assertEqual(response.data.get('author'), self.profile.user.id)
+ self.assertIsNotNone(response.data.get('last_message'))
+ self.assertIsNotNone(response.data.get('pubdate'))
+
+ def test_new_topic_with_anonymous(self):
+ """
+ Post a new topic in a forum with an anonymous user.
+ """
+ self.create_multiple_forums()
+ data = {
+ 'title': 'Flask 4 Ever !',
+ 'subtitle': 'Is it the best framework ?',
+ 'text': 'I head that Flask is the best framework ever, is that true ?',
+ 'forum': 1
+ }
+ self.client = APIClient()
+ response = self.client.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_new_topic_private_forum(self):
+ """
+ Post a new topic in a private forum (staff only) with an anonymous user, normal user and staff user.
+ """
+ profile = ProfileFactory()
+ self.group_staff.user_set.add(profile.user)
+ category, forum = create_category(self.group_staff)
+ data = {
+ 'title': 'Have you seen the guy flooding ?',
+ 'subtitle': 'He is asking to many question about flask.',
+ 'text': 'Should we ban him ? I think we should.',
+ 'forum': forum.id
+ }
+
+ # Anonymous
+ self.client = APIClient()
+ response = self.client.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ # User
+ response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ # Staff
+ response = self.client_authenticated_staff.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ def test_new_topic_with_banned_user(self):
+
+ profile = ProfileFactory()
+ profile.can_read = False
+ profile.can_write = False
+ profile.save()
+ self.create_multiple_forums()
+ data = {
+ 'title': 'Flask 4 Ever !',
+ 'subtitle': 'Is it the best framework ?',
+ 'text': 'I head that Flask is the best framework ever, is that true ?',
+ 'forum': 1
+ }
+
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+ response = self.client.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_new_topic_without_title(self):
+ """
+ Try to post a new topic in a forum without the title
+ """
+ self.create_multiple_forums()
+ data = {
+ 'subtitle': 'Is it the best framework ?',
+ 'text': 'I head that Flask is the best framework ever, is that true ?',
+ 'forum': 1
+ }
+
+ response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_new_topic_without_subtitle(self):
+ """
+ Try to post a new topic in a forum without the title
+ """
+ self.create_multiple_forums()
+ data = {
+ 'title': 'Flask 4 Ever !',
+ 'text': 'I head that Flask is the best framework ever, is that true ?',
+ 'forum': 1
+ }
+
+ response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ def test_new_topic_without_text(self):
+ """
+ Try to post a new topic in a forum without the text.
+ """
+ self.create_multiple_forums()
+ data = {
+ 'title': 'Flask 4 Ever !',
+ 'subtitle': 'Is it the best framework ?',
+ 'forum': 1
+ }
+
+ response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_new_topic_in_unknow_forum(self):
+ """
+ Try to post a new topic in a forum that does not exists.
+ """
+ self.create_multiple_forums()
+ data = {
+ 'title': 'Flask 4 Ever !',
+ 'subtitle': 'Is it the best framework ?',
+ 'text': 'I head that Flask is the best framework ever, is that true ?',
+ 'forum': 666
+ }
+
+ response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_details_topic(self):
+ """
+ Get details of a topic.
+ """
+ topic = self.create_multiple_forums_with_topics(1, 1)
+
+ self.client = APIClient()
+ response = self.client.get(reverse('api:forum:detail-topic', args=(topic.id,)))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('title'), topic.title)
+ self.assertEqual(response.data.get('subtitle'), topic.subtitle)
+ self.assertEqual(response.data.get('forum'), topic.forum.id)
+ self.assertIsNotNone(response.data.get('title'))
+ self.assertIsNotNone(response.data.get('forum'))
+
+ def test_details_unknown_topic(self):
+ """
+ Get details of a topic that doesw not exist.
+ """
+
+ self.client = APIClient()
+ response = self.client.get(reverse('api:forum:detail-topic', args=[666]))
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_details_topic_private(self):
+ """
+ Tries to get details of a topic that is in a private forum.
+ """
+ category, forum = create_category(self.group_staff)
+ topic = add_topic_in_a_forum(forum, self.staff)
+
+ # Anonymous
+ self.client = APIClient()
+ response = self.client.get(reverse('api:forum:detail-topic', args=[topic.id]))
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ # User
+ response = self.client_authenticated.get(reverse('api:forum:detail-topic', args=[topic.id]))
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ # Staff
+ response = self.client_authenticated_staff.get(reverse('api:forum:detail-topic', args=[topic.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+
+# Édite un sujet qvec user en ls
+# Édite un sujet avec user banni
+# Editer dans un forum privé ? Verifier les auths
+# TODO
+
+ def test_update_topic_details_title(self):
+ """
+ Updates title of a topic.
+ """
+ data = {
+ 'title': 'Mon nouveau titre'
+ }
+ topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
+ response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ print('test_update_topic_details_title')
+ print(response)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('title'), data.get('title'))
+
+ def test_update_topic_details_title_empty(self):
+ """
+ Updates title of a topic, tries to put an empty sting
+ """
+ data = {
+ 'title': ''
+ }
+ topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
+ response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_update_topic_details_subtitle(self):
+ """
+ Updates subtitle of a topic.
+ """
+ data = {
+ 'subtitle': 'Mon nouveau sous-titre'
+ }
+ topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
+ response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('subtitle'), data.get('subtitle'))
+
+ def test_update_topic_anonymous(self):
+ """
+ Tries to update a Topic with an anonymous user.
+ """
+ data = {
+ 'title': 'Mon nouveau titre'
+ }
+ topic = self.create_multiple_forums_with_topics(1, 1)
+ self.client = APIClient()
+ response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_update_topic_staff(self):
+ """
+ Updates title of a topic with a staff member.
+ """
+ data = {
+ 'title': 'Mon nouveau titre'
+ }
+ topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
+ response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('title'), data.get('title'))
+
+ def test_update_topic_other_user(self):
+ """
+ Tries to update title of a topic posted by another user.
+ """
+ data = {
+ 'title': 'Mon nouveau titre'
+ }
+ profile = ProfileFactory()
+ topic = self.create_multiple_forums_with_topics(1, 1, profile)
+ response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_update_unknown_topic(self):
+ """
+ Tries to update title of a non existing topic.
+ """
+ data = {
+ 'title': 'Mon nouveau titre'
+ }
+ response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[666]), data)
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_update_topic_forum(self):
+ """
+ Tries to move (change forum in which the topic is) with different users.
+ """
+ data = {
+ 'forum': 5
+ }
+ profile = ProfileFactory()
+ self.create_multiple_forums_with_topics(5, 1, profile)
+ topic = Topic.objects.filter(forum=1).first()
+
+ # Anonymous
+ self.client = APIClient()
+ response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ # User
+ response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ # Staff
+ response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('forum'), data.get('forum'))
+
+ def test_update_topic_lock(self):
+ """
+ Tries to lock a Topic with different users.
+ """
+ data = {
+ 'is_locked': True
+ }
+ topic = self.create_multiple_forums_with_topics(1, 1)
+
+ self.client = APIClient()
+ response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertTrue(response.data.get('is_locked'))
+
+ def test_update_topic_solve(self):
+ """
+ Tries to solve a Topic with different users.
+ """
+ data = {
+ 'is_solved': True
+ }
+ topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
+
+ self.client = APIClient()
+ response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ # Author
+ response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertTrue(response.data.get('is_solved'))
+
+ # Other user
+ other_profile = ProfileFactory()
+ client_oauth2 = create_oauth2_client(other_profile.user)
+ client_other_user = APIClient()
+ authenticate_client(client_other_user, client_oauth2, other_profile.user.username, 'hostel77')
+
+ response = client_other_user.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ # Staff
+ response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertTrue(response.data.get('is_solved'))
+
+ def test_list_of_posts_unknown(self):
+ """
+ Tries to get a list of posts in an unknown topic
+ """
+ response = self.client.get(reverse('api:forum:list-post', args=[666]))
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_list_of_posts(self):
+ """
+ Gets list of posts in a topic.
+ """
+ # A post is already included with the topic
+ topic, posts = self.create_topic_with_post(REST_PAGE_SIZE - 1)
+
+ response = self.client.get(reverse('api:forum:list-post', args=[topic.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_posts_private_forum(self):
+ """
+ Get a list of posts in a topic of a private forum.
+ """
+ group = Group.objects.create(name="DummyGroup_1")
+
+ profile = ProfileFactory()
+ group.user_set.add(profile.user)
+ category, forum = create_category(group)
+ topic = add_topic_in_a_forum(forum, profile)
+ # def add_topic_in_a_forum(forum, profile, is_sticky=False, is_solved=False, is_locked=False):
+
+ response = self.client.get(reverse('api:forum:list-post', args=[topic.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 1)
+ self.assertEqual(len(response.data.get('results')), 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_posts_private_forum_user(self):
+ """
+ Tries to get a list of posts in a topic of a private forum with a normal user.
+ """
+ profile = ProfileFactory()
+ self.group_staff.user_set.add(profile.user)
+ category, forum = create_category(self.group_staff)
+ topic = add_topic_in_a_forum(forum, profile)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-post', args=[topic.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 1)
+ self.assertEqual(len(response.data.get('results')), 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_posts_with_several_pages(self):
+ """
+ Gets list of posts with several pages in the database.
+ """
+ topic, posts = self.create_topic_with_post(REST_PAGE_SIZE + 1)
+
+ response = self.client.get(reverse('api:forum:list-post', args=[topic.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 2) # Note : when creating a Topic a first post is created, explaining the +1
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+
+ response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 2)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), 2)
+
+ def test_list_of_posts_for_a_page_given(self):
+ """
+ Gets list of posts with several pages and gets a page different that the first one.
+ """
+ topic, posts = self.create_topic_with_post(REST_PAGE_SIZE + 1)
+
+ response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 12)
+ self.assertEqual(len(response.data.get('results')), 2)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+
+ def test_list_of_posts_for_a_wrong_page_given(self):
+ """
+ Gets an error when the posts asks a wrong page.
+ """
+ topic, posts = self.create_topic_with_post(1)
+ response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_list_of_posts_with_a_custom_page_size(self):
+ """
+ Gets list of forums with a custom page size. DRF allows to specify a custom
+ size for the pagination.
+ """
+ topic, posts = self.create_topic_with_post((REST_PAGE_SIZE * 2) - 1)
+ print (topic)
+
+ page_size = 'page_size'
+ response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?{}=20'.format(page_size))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 20)
+ self.assertEqual(len(response.data.get('results')), 20)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
+
+ def test_list_of_posts_in_topic_with_a_wrong_custom_page_size(self):
+ """
+ Gets list of posts with a custom page size but not good according to the
+ value in settings.
+ """
+ page_size_value = REST_MAX_PAGE_SIZE + 1
+ topic, posts = self.create_topic_with_post(page_size_value)
+
+ response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page_size={}'.format(page_size_value))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ # We will have page_size_value + 1 because a post is added at topic creation.
+ self.assertEqual(response.data.get('count'), page_size_value + 1)
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
+
+ def test_list_of_posts_in_unknown_topic(self):
+ """
+ Tries to list the posts of an non existing Topic.
+ """
+
+ response = self.client.get(reverse('api:forum:list-post', args=[666]))
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_list_of_user_topics_empty(self):
+ """
+ Gets empty list of topic that the user created.
+ """
+ response = self.client_authenticated.get(reverse('api:forum:list-usertopic'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+ self.assertEqual(response.data.get('results'), [])
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_user_topics(self):
+ """
+ Gets list of user's topics not empty in the database.
+ """
+ self.create_multiple_forums_with_topics(10, 1, self.profile)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-usertopic'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 10)
+ self.assertEqual(len(response.data.get('results')), 10)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_user_topics_with_several_pages(self):
+ """
+ Gets list of user's topics with several pages in the database.
+ """
+ self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-usertopic'))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), 1)
+
+ def test_list_of_user_topics_for_a_page_given(self):
+ """
+ Gets list of user's topics with several pages and gets a page different that the first one.
+ """
+ self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 11)
+ self.assertEqual(len(response.data.get('results')), 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+
+ def test_list_of_users_topics_for_a_wrong_page_given(self):
+ """
+ Gets an error when the user's topics asks a wrong page.
+ """
+ response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_list_of_user_topics_with_a_custom_page_size(self):
+ """
+ Gets list of user's topics with a custom page size. DRF allows to specify a custom
+ size for the pagination.
+ """
+ self.create_multiple_forums_with_topics(REST_PAGE_SIZE * 2, 1, self.profile)
+
+ page_size = 'page_size'
+ response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?{}=20'.format(page_size))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 20)
+ self.assertEqual(len(response.data.get('results')), 20)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
+
+ def test_list_of_user_topics_with_a_wrong_custom_page_size(self):
+ """
+ Gets list of user's topic with a custom page size but not good according to the
+ value in settings.
+ """
+ page_size_value = REST_MAX_PAGE_SIZE + 1
+ self.create_multiple_forums_with_topics(page_size_value, 1, self.profile)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page_size={}'.format(page_size_value))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), page_size_value)
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
+
+ def test_list_of_user_topics_anonymous(self):
+ """
+ Tries to get a list of users topic with an anonymous user.
+ """
+ self.client = APIClient()
+ response = self.client.get(reverse('api:forum:list-usertopic'))
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+
+# DONE Créer un message 200
+# DONE Créer un message avec un contenu vide
+# DONECréer un message dans un sujet qui n'existe pas
+# DONE Créer un message en anonymous
+# DONE Créer un message dans un forum privé en user
+# DONE Créer un message dans un forum privé en staff
+# Créer un message dans un sujet fermé en user
+# Créer un message dans un sujet fermé en staff
+# Créer un message pour tester l'antiflood
+
+
+ def test_create_post_with_no_field(self):
+ """
+ Creates a post in a topic but not with according field.
+ """
+ topic = self.create_multiple_forums_with_topics(1, 1)
+ response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.id]), {})
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_create_post_with_empty_field(self):
+ """
+ Creates a post in a topic but with no text.
+ """
+ data = {
+ 'text': ''
+ }
+ topic = self.create_multiple_forums_with_topics(1, 1)
+ response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_create_post_unauthenticated(self):
+ """
+ Creates a post in a topic with unauthenticated client.
+ """
+ data = {
+ 'text': 'Welcome to this post!'
+ }
+
+ topic = self.create_multiple_forums_with_topics(1, 1)
+ self.client = APIClient()
+ with transaction.atomic():
+ response = self.client.post(reverse('api:forum:list-post', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_create_post_with_bad_topic_id(self):
+ """
+ Creates a post in a topic with a bad topic id.
+ """
+ data = {
+ 'text': 'Welcome to this post!'
+ }
+ response = self.client_authenticated.post(reverse('api:forum:list-post', args=[666]), data)
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_create_post(self):
+ """
+ Creates a post in a topic.
+ """
+ data = {
+ 'text': 'Welcome to this post!'
+ }
+ topic = self.create_multiple_forums_with_topics(1, 1)
+ response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ post = Post.objects.filter(topic=topic.id).last()
+
+ self.assertEqual(response.data.get('text'), data.get('text'))
+ self.assertEqual(response.data.get('text'), post.text)
+ self.assertEqual(response.data.get('is_useful'), post.is_useful)
+ self.assertEqual(response.data.get('author'), post.author.id)
+ self.assertEqual(response.data.get('position'), post.position)
+
+ def test_failure_post_in_a_forum_we_cannot_read(self):
+ """
+ Tries to create a post in a private topic with a normal user.
+ """
+ print('--------------------------- test rate--------------')
+ profile = StaffProfileFactory()
+ category, forum = create_category(self.group_staff)
+ topic = add_topic_in_a_forum(forum, profile)
+ data = {
+ 'text': 'Welcome to this post!'
+ }
+ response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.pk]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_post_in_a_private_forum(self):
+ """
+ Post in a private topic with a user that has access right.
+ """
+
+ category, forum = create_category(self.group_staff)
+ topic = add_topic_in_a_forum(forum, self.staff)
+ data = {
+ 'text': 'Welcome to this post!'
+ }
+ response = self.client_authenticated_staff.post(reverse('api:forum:list-post', args=[topic.pk,]), data)
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ post = Post.objects.filter(topic=topic.id).last()
+ self.assertEqual(response.data.get('text'), data.get('text'))
+ self.assertEqual(response.data.get('text'), post.text)
+ self.assertEqual(response.data.get('is_useful'), post.is_useful)
+ self.assertEqual(response.data.get('author'), post.author.id)
+ self.assertEqual(response.data.get('position'), post.position)
+
+ def test_new_post_user_with_restrictions(self):
+ """
+ Try to post a new post with an user that has some restrictions .
+ """
+
+ # Banned
+ profile = ProfileFactory()
+ profile.can_read = False
+ profile.can_write = False
+ profile.save()
+ topic = self.create_multiple_forums_with_topics(1, 1)
+ data = {
+ 'text': 'I head that Flask is the best framework ever, is that true ?'
+ }
+
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+ response = self.client.post(reverse('api:forum:list-post', args=[topic.id,]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ # Read only
+ profile = ProfileFactory()
+ profile.can_write = False
+ profile.save()
+
+ self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
+ response = self.client.post(reverse('api:forum:list-post', args=[topic.id,]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_detail_post(self):
+ """
+ Gets all information about a post.
+ """
+
+ topic, posts = self.create_topic_with_post()
+ post = posts[0]
+ response = self.client.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(post.id, response.data.get('id'))
+ self.assertIsNotNone(response.data.get('text'))
+ self.assertIsNotNone(response.data.get('text_html'))
+ self.assertIsNotNone(response.data.get('pubdate'))
+ self.assertIsNone(response.data.get('update'))
+ print('test_detail_post')
+ print(response.data)
+ self.assertEqual(post.position, response.data.get('position'))
+ self.assertEqual(topic.author.id, response.data.get('author'))
+
+ def test_detail_of_a_private_post_not_present(self):
+ """
+ Gets an error 404 when the post isn't present in the database.
+ """
+ topic = self.create_multiple_forums_with_topics(1, 1)
+ response = self.client.get(reverse('api:forum:detail-post', args=[topic.id, 666]))
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_detail_of_private_post(self):
+ """
+ Tries to get all the data about a post in a private topic (and forum) with different users.
+ """
+ category, forum = create_category(self.group_staff)
+ topic = add_topic_in_a_forum(forum, self.profile)
+ post = Post.objects.filter(topic=topic.id).first()
+
+ # Anonymous
+ self.client = APIClient()
+ response = self.client.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ # User
+ response = self.client_authenticated.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ # Staff user
+ response = self.client_authenticated_staff.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ def test_list_of_member_posts_empty(self):
+ """
+ Gets empty list of posts that that a specified member created.
+ """
+ response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+ self.assertEqual(response.data.get('results'), [])
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_member_posts(self):
+ """
+ Gets list of a member posts not empty in the database.
+ """
+ self.create_multiple_forums_with_topics(10, 1, self.profile)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 10)
+ self.assertEqual(len(response.data.get('results')), 10)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+
+ def test_list_of_staff_posts(self):
+ """
+ Gets list of a staff posts.
+ """
+
+ profile = StaffProfileFactory()
+ category, forum = create_category(self.group_staff)
+ topic = add_topic_in_a_forum(forum, profile)
+
+ # Anonymous user cannot see staff private post.
+ self.client = APIClient()
+ response = self.client.get(reverse('api:forum:list-memberpost', args=[profile.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+
+ # Same for normal user
+ response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[profile.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 0)
+
+ response = self.client_authenticated_staff.get(reverse('api:forum:list-memberpost', args=[profile.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 1)
+
+ def test_list_of_member_posts_with_several_pages(self):
+ """
+ Gets list of a member topics with several pages in the database.
+ """
+ self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+ self.assertEqual(len(response.data.get('results')), 1)
+
+ def test_list_of_member_posts_for_a_page_given(self):
+ """
+ Gets list of a member topics with several pages and gets a page different that the first one.
+ """
+
+ self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
+ response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id])+ '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 11)
+ self.assertEqual(len(response.data.get('results')), 1)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNotNone(response.data.get('previous'))
+
+ def test_list_of_member_post_for_a_wrong_page_given(self):
+ """
+ Gets an error when the member posts asks a wrong page.
+ """
+ response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?page=2')
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_list_of_member_posts_with_a_custom_page_size(self):
+ """
+ Gets list of user's posts with a custom page size. DRF allows to specify a custom
+ size for the pagination.
+ """
+ # When a topic is created, a post is also created.
+ self.create_topic_with_post((REST_PAGE_SIZE * 2) - 1, self.profile)
+
+ page_size = 'page_size'
+ response = self.client.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?{}=20'.format(page_size))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), 20)
+ self.assertEqual(len(response.data.get('results')), 20)
+ self.assertIsNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
+
+ def test_list_of_member_posts_with_a_wrong_custom_page_size(self):
+ """
+ Gets list of member posts with a custom page size but not good according to the
+ value in settings.
+ """
+ page_size_value = REST_MAX_PAGE_SIZE + 1
+ self.create_multiple_forums_with_topics(page_size_value, 1, self.profile)
+
+ response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?page_size={}'.format(page_size_value))
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('count'), page_size_value)
+ self.assertIsNotNone(response.data.get('next'))
+ self.assertIsNone(response.data.get('previous'))
+ self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
+
+ def test_list_of_unknow_member_posts(self):
+ """
+ Gets empty list of posts for a member that does not exists.
+ """
+ response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[666]))
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_alert_post(self):
+ """
+ Tries to alert a post in a public forum with different type of users
+ """
+ profile = ProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ another_profile = ProfileFactory()
+ post = PostFactory(topic=topic, author=another_profile.user, position=1)
+ data = {
+ 'text': 'There is a guy flooding about Flask, con you do something about it ?'
+ }
+
+ self.client = APIClient()
+ response = self.client.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ response = self.client_authenticated.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ alerte = Alert.objects.latest('pubdate')
+ self.assertEqual(alerte.text, data.get('text'))
+ self.assertEqual(alerte.author, self.profile.user)
+ self.assertEqual(alerte.comment.id, post.id)
+
+
+ def test_alert_post_in_private_forum(self):
+ """
+ Tries to alert a post in a public forum with different type of users
+ """
+ profile = StaffProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ post = PostFactory(topic=topic, author=profile.user, position=1)
+ data = {
+ 'text': 'There is a guy flooding about Flask, con you do something about it ?'
+ }
+
+ self.client = APIClient()
+ response = self.client.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ response = self.client_authenticated.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ response = self.client_authenticated_staff.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ alerte = Alert.objects.latest('pubdate')
+ self.assertEqual(alerte.text, data.get('text'))
+ self.assertEqual(alerte.author, self.client_authenticated_staff)
+ self.assertEqual(alerte.comment, post.id)
+
+ def test_alert_post_not_found(self):
+ """
+ Tries to alert a post in a public forum with different type of users
+ """
+ profile = ProfileFactory()
+ category, forum = create_category()
+ topic = add_topic_in_a_forum(forum, profile)
+ post = PostFactory(topic=topic, author=profile.user, position=1)
+ data = {
+ 'text': 'There is a guy flooding about Flask, con you do something about it ?'
+ }
+
+ response = self.client_authenticated.post(reverse('api:forum:alert-post', args=[topic.id, 666]), data)
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+# Edite un message d'un sujet fermé user
+# Edite un message d'un sujet fermé staff
+
+# TODO
+
+
+ def test_update_post_anonymous(self):
+ """
+ Tries to update a post with anonymous user.
+ """
+ data = {
+ 'text': 'I made an error I want to edit.'
+ }
+ self.client = APIClient();
+ topic, posts = self.create_topic_with_post()
+ response = self.client.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_update_post_other_user(self):
+ """
+ Tries to update a post with another user that the one who posted on the first time.
+ """
+ data = {
+ 'text': 'I made an error I want to edit.'
+ }
+ another_profile = ProfileFactory()
+ topic, posts = self.create_topic_with_post(REST_PAGE_SIZE, another_profile)
+ print('test_update_post_other_user')
+ response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_update_post(self):
+ """
+ Updates a post with user.
+ """
+ data = {
+ 'text': 'I made an error I want to edit.'
+ }
+ topic, posts = self.create_topic_with_post(1, self.profile)
+ response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('text'), data.get('text'))
+
+ def test_update_post_staff(self):
+ """
+ Update a post with a staff user.
+ """
+ data = {
+ 'text': 'I am Vladimir Lupin, I do want I want.'
+ }
+ topic, posts = self.create_topic_with_post()
+ response = self.client_authenticated_staff.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('text'), data.get('text'))
+
+ def test_update_unknow_post(self):
+ """
+ Tries to update post that does not exist.
+ """
+ data = {
+ 'text': 'I made an error I want to edit.'
+ }
+ response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[666, 42]), data)
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_update_post_in_private_topic(self):
+ """
+ Tries to update a post in a private forum (and topic) with anonymous user.
+ """
+ data = {
+ 'text': 'I made an error I want to edit.'
+ }
+ self.client = APIClient();
+ category, forum = create_category(self.group_staff)
+ topic = add_topic_in_a_forum(forum, self.staff)
+ post = PostFactory(topic=topic, author=self.staff.user, position=1)
+
+ # With anonymous
+ response = self.client.put(reverse('api:forum:detail-post', args=[topic.id, post.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ # With user
+ response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[topic.id, post.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ # With staff (member of private forum)
+ response = self.client_authenticated_staff.put(reverse('api:forum:detail-post', args=[topic.id, post.id]), data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(data.get('text'), response.data.get('text'))
+
+def create_oauth2_client(user):
+ client = Application.objects.create(user=user,
+ client_type=Application.CLIENT_CONFIDENTIAL,
+ authorization_grant_type=Application.GRANT_PASSWORD)
+ client.save()
+ return client
+
+
+def authenticate_client(client, client_auth, username, password):
+ client.post('/oauth2/token/', {
+ 'client_id': client_auth.client_id,
+ 'client_secret': client_auth.client_secret,
+ 'username': username,
+ 'password': password,
+ 'grant_type': 'password'
+ })
+ access_token = AccessToken.objects.get(user__username=username)
+ client.credentials(HTTP_AUTHORIZATION='Bearer {}'.format(access_token))
+
+# TODO
+# Reorganiser le code de test en differentes classes, reordonner les tests
+# Gérer le champ update (date) lors de l'edit
+# Gérer l'antispam
+# identifer quand masquer les messages (message modéré ou masqué par son auteur)
+# empecher les ls et les ban de faire des alertes
+# Gestion de tags (post/edit/details)
+# Tests qui ne passent pas
+# Style / PEP8
+
+# TESTS MANQUANTS
+# Vérifier que l'on affiche pas le text hidden ou l'adresse ip
+# Créer un topic avec des tags (ajouter le test), éditer les tags
+# Tester le cas ou un user veux vider le contenu de son message
+# Ajouter le cas ou le staff ou un user masque son message, mais un autre user ne peut pas le faire
+# Poste dans un sujet fermé (3 roles)
diff --git a/zds/forum/api/urls.py b/zds/forum/api/urls.py
new file mode 100644
index 0000000000..a30fdb4665
--- /dev/null
+++ b/zds/forum/api/urls.py
@@ -0,0 +1,16 @@
+from django.conf.urls import url
+
+from .views import PostKarmaView, ForumListAPI, ForumDetailAPI, PostListAPI, TopicListAPI, TopicDetailAPI, UserTopicListAPI, MemberPostListAPI, UserPostListAPI, PostDetailAPI, PostAlertAPI
+urlpatterns = [
+ url(r'^$', ForumListAPI.as_view(), name='list'),
+ url(r'^(?P[0-9]+)/?$', ForumDetailAPI.as_view(), name='detail'),
+ url(r'^sujets/?$', TopicListAPI.as_view(), name='list-topic'),
+ url(r'^membre/sujets/?$', UserTopicListAPI.as_view(), name='list-usertopic'),
+ url(r'^message/(?P\d+)/karma/?$', PostKarmaView.as_view(), name='post-karma'),
+ url(r'^sujets/(?P[0-9]+)/messages?$', PostListAPI.as_view(), name='list-post'),
+ url(r'^sujets/(?P[0-9]+)/?$', TopicDetailAPI.as_view(), name='detail-topic'),
+ url(r'^membres/(?P[0-9]+)/messages/?$', MemberPostListAPI.as_view(), name='list-memberpost'),
+ url(r'^membres/messages/?$', UserPostListAPI.as_view(), name='list-userpost'),
+ url(r'^sujets/(?P[0-9]+)/messages/(?P[0-9]+)/?$', PostDetailAPI.as_view(), name='detail-post'),
+ url(r'^sujets/(?P[0-9]+)/messages/(?P[0-9]+)/alert/?$', PostAlertAPI.as_view(), name='alert-post')
+]
diff --git a/zds/forum/api/views.py b/zds/forum/api/views.py
new file mode 100644
index 0000000000..378dbe256b
--- /dev/null
+++ b/zds/forum/api/views.py
@@ -0,0 +1,647 @@
+# coding: utf-8
+
+from zds.member.api.permissions import CanReadTopic, CanReadPost, CanReadForum, CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, IsOwnerOrReadOnly, IsStaffUser
+from zds.utils.api.views import KarmaView
+from zds.forum.models import Post, Forum, Topic, Category
+import datetime
+from django.core.cache import cache
+from django.db.models.signals import post_save, post_delete
+from django.http import Http404
+from rest_framework import filters
+from rest_framework.generics import ListAPIView, ListCreateAPIView, RetrieveUpdateAPIView, RetrieveAPIView, CreateAPIView
+from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor
+from rest_framework_extensions.cache.decorators import cache_response
+from rest_framework_extensions.etag.decorators import etag
+from rest_framework_extensions.key_constructor import bits
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.permissions import IsAuthenticatedOrReadOnly, AllowAny, IsAuthenticated
+from dry_rest_permissions.generics import DRYPermissions
+from zds.api.bits import DJRF3xPaginationKeyBit, UpdatedAtKeyBit
+from zds.utils import slugify
+from zds.forum.api.serializer import ForumSerializer, TopicSerializer, TopicCreateSerializer, TopicUpdateSerializer, TopicUpdateStaffSerializer, PostSerializer, PostCreateSerializer, PostUpdateSerializer, AlertSerializer
+from zds.forum.api.permissions import IsStaffUser, IsOwnerOrIsStaff, CanWriteInForum, CanWriteInTopic
+from zds.member.models import User
+from itertools import chain
+
+class PostKarmaView(KarmaView):
+ queryset = Post.objects.all()
+ permission_classes = (IsAuthenticatedOrReadOnly, CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, CanReadPost)
+
+
+class PagingSearchListKeyConstructor(DefaultKeyConstructor):
+ pagination = DJRF3xPaginationKeyBit()
+ list_sql_query = bits.ListSqlQueryKeyBit()
+ unique_view_id = bits.UniqueViewIdKeyBit()
+ user = bits.UserKeyBit()
+ updated_at = UpdatedAtKeyBit('api_updated_forum')
+
+
+class DetailKeyConstructor(DefaultKeyConstructor):
+ format = bits.FormatKeyBit()
+ language = bits.LanguageKeyBit()
+ retrieve_sql_query = bits.RetrieveSqlQueryKeyBit()
+ unique_view_id = bits.UniqueViewIdKeyBit()
+ user = bits.UserKeyBit()
+ updated_at = UpdatedAtKeyBit('api_updated_forum')
+
+
+def change_api_forum_updated_at(sender=None, instance=None, *args, **kwargs):
+ cache.set('forum_updated_tag', datetime.datetime.utcnow())
+
+
+post_save.connect(receiver=change_api_forum_updated_at, sender=Forum)
+post_delete.connect(receiver=change_api_forum_updated_at, sender=Forum)
+
+
+class ForumListAPI(ListCreateAPIView):
+ """
+ Profile resource to list all forum.
+ """
+ serializer_class = ForumSerializer
+ list_key_func = PagingSearchListKeyConstructor()
+
+ @etag(list_key_func)
+ @cache_response(key_func=list_key_func)
+ def get(self, request, *args, **kwargs):
+ """
+ Lists all forum in the system.
+ ---
+
+ parameters:
+ - name: page
+ description: Restricts output to the given page number.
+ required: false
+ paramType: query
+ - name: page_size
+ description: Sets the number of forum per page.
+ required: false
+ paramType: query
+ responseMessages:
+ - code: 404
+ message: Not Found
+ """
+ return self.list(request, *args, **kwargs)
+
+ def get_queryset(self):
+ public_forums = Forum.objects.filter(group__isnull=True).order_by('position_in_category')
+ private_forums = Forum.objects.filter(group__in=self.request.user.groups.all()).order_by('position_in_category')
+ return public_forums | private_forums
+
+ def get_permissions(self):
+ permission_classes = [CanReadForum] # TODO style plus joli ?
+ return [permission() for permission in permission_classes]
+
+
+class ForumDetailAPI(RetrieveAPIView):
+ """
+ Profile resource to display details of a forum.
+ ---
+ """
+
+ queryset = Forum.objects.all()
+ obj_key_func = DetailKeyConstructor()
+ serializer_class = ForumSerializer
+
+ @etag(obj_key_func)
+ @cache_response(key_func=obj_key_func)
+ def get(self, request, *args, **kwargs):
+ """
+ Gets a forum given by its identifier.
+ ---
+ responseMessages:
+ - code: 404
+ message: Not Found
+ """
+ forum = self.get_object()
+
+ return self.retrieve(request, *args, **kwargs)
+
+ def get_serializer_class(self):
+ return ForumSerializer
+
+ def get_permissions(self):
+ permission_classes = [CanReadForum] # TODO style plus joli ?
+ return [permission() for permission in permission_classes]
+
+
+class TopicListAPI(ListCreateAPIView):
+ """
+ Profile resource to list all topics (GET) or to create a topic (POST)
+ """
+ queryset = Topic.objects.all()
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('forum', 'author', 'tags__title')
+ list_key_func = PagingSearchListKeyConstructor()
+
+ @etag(list_key_func)
+ @cache_response(key_func=list_key_func)
+ def get(self, request, *args, **kwargs):
+ """
+ Lists all topic in a forum.
+ ---
+
+ parameters:
+ - name: page
+ description: Restricts output to the given page number.
+ required: false
+ paramType: query
+ - name: page_size
+ description: Sets the number of forum per page.
+ required: false
+ paramType: query
+ - name: forum
+ description: Filters by forum id.
+ required: false
+ - name: author
+ description: Filters by author id.
+ required: false
+ - name: tags__title
+ description: Filters by tag name.
+ required: false
+ responseMessages:
+ - code: 404
+ message: Not Found
+ """
+
+ return self.list(request, *args, **kwargs)
+
+ def post(self, request, *args, **kwargs):
+ """
+ Creates a new topic.
+ ---
+
+ parameters:
+ - name: Authorization
+ description: Bearer token to make an authenticated request.
+ required: true
+ paramType: header
+ - name: title
+ description: Title of the Topic.
+ required: true
+ paramType: form
+ - name: subtitle
+ description: Subtitle of the Topic.
+ required: false
+ paramType: form
+ - name: forum
+ description: Identifier of the forum in which the Topic should be posted.
+ required: false
+ paramType: form
+ - name: text
+ description: Content of the first post in markdown.
+ required: true
+ paramType: form
+ - name: tags
+ description: To add a tag, specify its taf identifier. Specify this parameter
+ several times to add several tags.
+ required: false
+ paramType: form
+ responseMessages:
+ - code: 400
+ message: Bad Request
+ - code: 401
+ message: Not Authenticated
+ - code: 403
+ message: Forbidden
+ """
+ author = request.user.id
+ serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
+ serializer.is_valid(raise_exception=True)
+ serializer.save(author_id=author)
+ headers = self.get_success_headers(serializer.data)
+ return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+
+ def get_serializer_class(self):
+ if self.request.method == 'GET':
+ return TopicSerializer
+ elif self.request.method == 'POST':
+ return TopicCreateSerializer
+
+ def get_permissions(self):
+ permission_classes = [CanReadForum]
+ if self.request.method == 'POST':
+ print('requete post')
+
+ #forum = Forum.objects.get(id=self.request.data.get('forum'))
+ #self.check_object_permissions(self.request, forum)
+ #permission_classes.append(CanReadAndWriteNowOrReadOnly)
+ permission_classes.append(CanWriteInForum)
+ permission_classes.append(CanReadAndWriteNowOrReadOnly)
+ return [permission() for permission in permission_classes]
+
+
+class UserTopicListAPI(ListAPIView):
+ """
+ Profile resource to list all topics from current user
+ """
+
+ serializer_class = TopicSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ('forum', 'tags__title')
+ list_key_func = PagingSearchListKeyConstructor()
+
+ @etag(list_key_func)
+ @cache_response(key_func=list_key_func)
+ def get(self, request, *args, **kwargs):
+ """
+ Lists all topic from current user.
+ ---
+
+ parameters:
+ - name: Authorization
+ description: Bearer token to make an authenticated request.
+ required: true
+ paramType: header
+ - name: page
+ description: Restricts output to the given page number.
+ required: false
+ paramType: query
+ - name: page_size
+ description: Sets the number of forum per page.
+ required: false
+ paramType: query
+ responseMessages:
+ - code: 401
+ message: Not Authenticated
+ - code: 404
+ message: Not Found
+ """
+ return self.list(request, *args, **kwargs)
+
+ def get_queryset(self):
+ topics = Topic.objects.filter(author=self.request.user)
+ return topics
+
+ def get_permissions(self):
+ permission_classes = [AllowAny, IsAuthenticated]
+ return [permission() for permission in permission_classes]
+
+
+class TopicDetailAPI(RetrieveUpdateAPIView):
+ """
+ Profile resource to display and update details of a given topic
+ """
+ queryset = Topic.objects.all()
+ obj_key_func = DetailKeyConstructor()
+
+ @etag(obj_key_func)
+ @cache_response(key_func=obj_key_func)
+ def get(self, request, *args, **kwargs):
+ """
+ Gets a topic given by its identifier.
+ ---
+ responseMessages:
+ - code: 401
+ message: Not Authenticated
+ - code: 403
+ message: Forbidden
+ - code: 404
+ message: Not Found
+ """
+ topic = self.get_object()
+
+ return self.retrieve(request, *args, **kwargs)
+
+ def put(self, request, *args, **kwargs):
+ """
+ Updates a topic. Said post must be owned by the authenticated member.
+ ---
+
+ parameters:
+ - name: Authorization
+ description: Bearer token to make an authenticated request.
+ required: true
+ paramType: header
+ - name: title
+ description: Title of the Topic.
+ required: false
+ paramType: form
+ - name: subtitle
+ description: Subtitle of the Topic.
+ required: false
+ paramType: form
+ - name: text
+ description: Content of the first post in markdown.
+ required: false
+ paramType: form
+ - name: tags
+ description: To add a tag, specify its taf identifier. Specify this parameter
+ several times to add several tags.
+ required: false
+ paramType: form
+ responseMessages:
+ - code: 400
+ message: Bad Request if you specify a bad identifier
+ - code: 401
+ message: Not Authenticated
+ - code: 403
+ message: Permission Denied
+ - code: 404
+ message: Not Found
+ """
+ return self.update(request, *args, **kwargs)
+
+ def get_serializer_class(self):
+ if self.request.method == 'GET':
+ return TopicSerializer
+ elif self.request.method == 'PUT':
+ if self.request.user.has_perm("forum.change_topic"):
+ return TopicUpdateStaffSerializer
+ else:
+ return TopicUpdateSerializer
+
+ def get_permissions(self):
+ permission_classes = []
+ if self.request.method == 'GET':
+ permission_classes.append(CanReadTopic)
+ elif self.request.method == 'PUT':
+ print(self.request.user)
+ permission_classes.append(IsOwnerOrIsStaff)
+ permission_classes.append(CanReadTopic)
+ return [permission() for permission in permission_classes]
+
+
+class PostListAPI(ListCreateAPIView):
+ """
+ Profile resource to list all messages in a topic
+ """
+ list_key_func = PagingSearchListKeyConstructor()
+
+ @etag(list_key_func)
+ @cache_response(key_func=list_key_func)
+ def get(self, request, *args, **kwargs):
+ """
+ Lists all posts in a topic
+ ---
+
+ parameters:
+ - name: page
+ description: Restricts output to the given page number.
+ required: false
+ paramType: query
+ - name: page_size
+ description: Sets the number of forum per page.
+ required: false
+ paramType: query
+ responseMessages:
+ - code: 401
+ message: Not Authenticated
+ - code: 403
+ message: Permission Denied
+ - code: 404
+ message: Not Found
+ """
+ return self.list(request, *args, **kwargs)
+ # TODO si message cache ? Le cacher dans l'API
+
+ def post(self, request, *args, **kwargs):
+ """
+ Creates a new post in a topic.
+ ---
+ parameters:
+ - name: Authorization
+ description: Bearer token to make an authenticated request.
+ required: true
+ paramType: header
+ - name: text
+ description: Content of the post in markdown.
+ required: true
+ paramType: form
+ - name: text
+ description: Content of the first post in markdown.
+ required: true
+ responseMessages:
+ - code: 400
+ message: Bad Request
+ - code: 401
+ message: Not Authenticated
+ - code: 403
+ message: Permission Denied
+ - code: 404
+ message: Not Found
+ """
+ author = request.user.id
+ topic = self.kwargs.get('pk')
+
+ serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
+ serializer.is_valid(raise_exception=True)
+ serializer.save(position=0, author_id=author, topic_id=topic)
+ headers = self.get_success_headers(serializer.data)
+ return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+
+ def get_serializer_class(self):
+ if self.request.method == 'GET':
+ return PostSerializer
+ elif self.request.method == 'POST':
+ return PostCreateSerializer
+
+ def get_queryset(self):
+ if self.request.method == 'GET':
+ posts = Post.objects.filter(topic=self.kwargs.get('pk'))
+ if posts.count() == 0:
+ raise Http404("Topic with pk {} was not found".format(self.kwargs.get('pk')))
+ return posts
+
+ def get_current_user(self):
+ return self.request.user.profile
+
+ def get_permissions(self):
+ permission_classes = [CanReadPost]
+ if self.request.method == 'POST':
+ permission_classes.append(CanReadAndWriteNowOrReadOnly)
+ permission_classes.append(CanWriteInTopic)
+
+ return [permission() for permission in permission_classes]
+
+
+class MemberPostListAPI(ListAPIView):
+ """
+ Profile resource to list all posts from a member
+ """
+ list_key_func = PagingSearchListKeyConstructor()
+ serializer_class = PostSerializer
+
+ @etag(list_key_func)
+ @cache_response(key_func=list_key_func)
+ def get(self, request, *args, **kwargs):
+ """
+ Lists all posts from a member
+ ---
+
+ parameters:
+ - name: page
+ description: Restricts output to the given page number.
+ required: false
+ paramType: query
+ - name: page_size
+ description: Sets the number of forum per page.
+ required: false
+ paramType: query
+ responseMessages:
+ - code: 404
+ message: Not Found
+ """
+ return self.list(request, *args, **kwargs)
+ # TODO fonctionne mais error xml sur certains post http://zds-anto59290.c9users.io/api/forums/membres/3/sujets
+
+ def get_queryset(self):
+ if self.request.method == 'GET':
+ try:
+ author = User.objects.get(pk = self.kwargs.get('pk'))
+ except User.DoesNotExist:
+ raise Http404("User with pk {} was not found".format(self.kwargs.get('pk')))
+
+ # Gets every post of author visible by current user
+ posts = Post.objects.get_all_messages_of_a_user(self.request.user, author)
+ return posts
+
+
+class UserPostListAPI(ListAPIView):
+ """
+ Profile resource to list all message from current user
+ """
+
+ list_key_func = PagingSearchListKeyConstructor()
+ serializer_class = PostSerializer
+
+ @etag(list_key_func)
+ @cache_response(key_func=list_key_func)
+ def get(self, request, *args, **kwargs):
+ """
+ Lists all posts from a current user.
+ ---
+ parameters:
+ - name: page
+ description: Restricts output to the given page number.
+ required: false
+ paramType: query
+ - name: page_size
+ description: Sets the number of forum per page.
+ required: false
+ paramType: query
+ responseMessages:
+ - code: 404
+ message: Not Found
+ """
+ return self.list(request, *args, **kwargs)
+
+ def get_queryset(self):
+ if self.request.method == 'GET':
+ posts = Post.objects.filter(author=self.request.user)
+ return posts
+
+
+class PostDetailAPI(RetrieveUpdateAPIView):
+ """
+ Profile resource to display details of given post
+ """
+
+ queryset = Post.objects.all()
+ obj_key_func = DetailKeyConstructor()
+
+ @etag(obj_key_func)
+ @cache_response(key_func=obj_key_func)
+ def get(self, request, *args, **kwargs):
+ """
+ Gets a post given by its identifier.
+ ---
+ responseMessages:
+ - code: 401
+ message: Not Authenticated
+ - code: 403
+ message: Permission Denied
+ - code: 404
+ message: Not Found
+ """
+ post = self.get_object()
+
+ return self.retrieve(request, *args, **kwargs)
+
+ def put(self, request, *args, **kwargs):
+ """
+ Updates a post. Said post must be owned by the authenticated member.
+ ---
+
+ parameters:
+ - name: Authorization
+ description: Bearer token to make an authenticated request.
+ required: true
+ paramType: header
+ - name: text
+ description: Content of the post in markdown.
+ required: true
+ paramType: form
+ responseMessages:
+ - code: 400
+ message: Bad Request if you specify a bad identifier
+ - code: 401
+ message: Not Authenticated
+ - code: 403
+ message: Permission Denied
+ - code: 404
+ message: Not Found
+ """
+ return self.update(request, *args, **kwargs)
+
+ def get_serializer_class(self):
+ if self.request.method == 'GET':
+ return PostSerializer
+ elif self.request.method == 'PUT':
+ return PostUpdateSerializer
+
+ def get_permissions(self):
+ permission_classes = [AllowAny, CanReadPost]
+ if self.request.method == 'PUT':
+ permission_classes.append(IsOwnerOrIsStaff)
+ permission_classes.append(CanReadAndWriteNowOrReadOnly)
+ return [permission() for permission in permission_classes]
+
+
+class PostAlertAPI(CreateAPIView):
+ """
+ Alert a topic post to the staff.
+ """
+
+ def post(self, request, *args, **kwargs):
+ """
+ Alert a topic post to the staff.
+ ---
+
+ parameters:
+ - name: Authorization
+ description: Bearer token to make an authenticated request.
+ required: true
+ paramType: header
+ - name: text
+ description: Content of the alert in markdown.
+ required: true
+ paramType: form
+ responseMessages:
+ - code: 400
+ message: Bad Request
+ - code: 401
+ message: Not Authenticated
+ - code: 403
+ message: Permission Denied
+ - code: 404
+ message: Not Found
+ """
+ author = request.user
+ try:
+ post = Post.objects.get(id = self.kwargs.get('pk'))
+ except Post.DoesNotExist:
+ raise Http404("Post with pk {} was not found".format(self.kwargs.get('pk')))
+
+ serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
+ serializer.is_valid(raise_exception=True)
+ serializer.save(comment=post, pubdate = datetime.datetime.now(), author = author)
+ headers = self.get_success_headers(serializer.data)
+ return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+
+ def get_permissions(self):
+ permission_classes = [CanReadPost, IsAuthenticated]
+ return [permission() for permission in permission_classes]
+
+ def get_serializer_class(self):
+ return AlertSerializer
From ba88ccca4c7b2ec94d412901c04397ff3087ac80 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 22:06:55 +0200
Subject: [PATCH 51/78] Update tests_models.py
---
zds/utils/tests/tests_models.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zds/utils/tests/tests_models.py b/zds/utils/tests/tests_models.py
index 9cd6b7d56d..725db944aa 100644
--- a/zds/utils/tests/tests_models.py
+++ b/zds/utils/tests/tests_models.py
@@ -2,7 +2,7 @@
from django.test import TestCase
from django.db import IntegrityError, transaction
-from zds.utils.validators import TagValidator
+from zds.utils.forms import TagValidator
from zds.utils.models import Tag
From e924cc33af2cb538893ada794d05febeaa1f2ceb Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 22:07:44 +0200
Subject: [PATCH 52/78] Update permissions.py
---
zds/member/api/permissions.py | 21 +--------------------
1 file changed, 1 insertion(+), 20 deletions(-)
diff --git a/zds/member/api/permissions.py b/zds/member/api/permissions.py
index 09548ef0ae..2fb1e31199 100644
--- a/zds/member/api/permissions.py
+++ b/zds/member/api/permissions.py
@@ -3,6 +3,7 @@
from rest_framework import permissions
from django.contrib.auth.models import AnonymousUser
+
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
@@ -85,24 +86,4 @@ class CanReadTopic(permissions.BasePermission):
"""
def has_object_permission(self, request, view, obj):
- print('can read topic')
- print(obj.forum.can_read(request.user))
return obj.forum.can_read(request.user)
-
-
-class CanReadForum(permissions.BasePermission):
- """
- Checks if the user can read that forum
- """
-
- def has_object_permission(self, request, view, obj):
- print('can read forum object')
- return obj.can_read(request.user)
-
-class CanReadPost(permissions.BasePermission):
- """
- Checks if the user can read that post
- """
-
- def has_object_permission(self, request, view, obj):
- return obj.topic.forum.can_read(request.user)
\ No newline at end of file
From 21141ef6c0c35055a98f3dedd206c1a16b297cd1 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 22:08:09 +0200
Subject: [PATCH 53/78] Update forms.py
---
zds/forum/forms.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/zds/forum/forms.py b/zds/forum/forms.py
index 64022ffb5b..4dcbd96ec9 100644
--- a/zds/forum/forms.py
+++ b/zds/forum/forms.py
@@ -9,8 +9,7 @@
from crispy_forms.layout import Layout, Field, Hidden
from crispy_forms.bootstrap import StrictButton
from zds.forum.models import Forum, Topic
-from zds.utils.forms import CommonLayoutEditor
-from zds.utils.validators import TagValidator
+from zds.utils.forms import CommonLayoutEditor, TagValidator
from django.utils.translation import ugettext_lazy as _
From 42b499692b03aa9b752e385f6298a2ca2dcb7c14 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 22:09:16 +0200
Subject: [PATCH 54/78] Update tests.py
---
zds/forum/api/tests.py | 1492 +---------------------------------------
1 file changed, 3 insertions(+), 1489 deletions(-)
diff --git a/zds/forum/api/tests.py b/zds/forum/api/tests.py
index c8cdf3d6cd..badfbb2a26 100644
--- a/zds/forum/api/tests.py
+++ b/zds/forum/api/tests.py
@@ -4,30 +4,20 @@
from django.core.cache import caches
from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group
-from django.db import transaction
from rest_framework import status
from rest_framework.test import APIClient
from rest_framework.test import APITestCase
-from oauth2_provider.models import Application, AccessToken
from rest_framework_extensions.settings import extensions_api_settings
-from zds.api.pagination import REST_PAGE_SIZE, REST_MAX_PAGE_SIZE, REST_PAGE_SIZE_QUERY_PARAM
-from zds.member.factories import ProfileFactory, StaffProfileFactory
-from zds.forum.models import Forum, Topic, Post
+
from zds.forum.factories import PostFactory
from zds.forum.tests.tests_views import create_category, add_topic_in_a_forum
-from zds.utils.models import CommentVote, Alert
+from zds.member.factories import ProfileFactory
+from zds.utils.models import CommentVote
class ForumPostKarmaAPITest(APITestCase):
def setUp(self):
-
self.client = APIClient()
- self.profile = ProfileFactory()
-
- client_oauth2 = create_oauth2_client(self.profile.user)
- self.client_authenticated = APIClient()
- authenticate_client(self.client_authenticated, client_oauth2, self.profile.user.username, 'hostel77')
-
caches[extensions_api_settings.DEFAULT_USE_CACHE].clear()
def test_failure_post_karma_with_client_unauthenticated(self):
@@ -206,1479 +196,3 @@ def test_get_post_voters(self):
self.assertEqual(1, len(response.data['dislike']['users']))
self.assertEqual(1, response.data['like']['count'])
self.assertEqual(1, response.data['dislike']['count'])
-
-
-class ForumAPITest(APITestCase):
- def setUp(self):
- self.client = APIClient()
-
- self.profile = ProfileFactory()
- client_oauth2 = create_oauth2_client(self.profile.user)
- self.client_authenticated = APIClient()
- authenticate_client(self.client_authenticated, client_oauth2, self.profile.user.username, 'hostel77')
-
- self.staff = StaffProfileFactory()
- client_oauth2 = create_oauth2_client(self.staff.user)
- self.client_authenticated_staff = APIClient()
- authenticate_client(self.client_authenticated_staff, client_oauth2, self.staff.user.username, 'hostel77')
-
- self.group_staff = Group.objects.filter(name="staff").first()
-
- caches[extensions_api_settings.DEFAULT_USE_CACHE].clear()
-
- def create_multiple_forums(self, number_of_forum=REST_PAGE_SIZE):
- for forum in xrange(0, number_of_forum):
- category, forum = create_category()
-
- def create_multiple_forums_with_topics(self, number_of_forum=REST_PAGE_SIZE, number_of_topic=REST_PAGE_SIZE, profile=None):
- if profile is None:
- profile = ProfileFactory()
- for forum in xrange(0, number_of_forum):
- category, forum = create_category()
- for topic in xrange(0, number_of_topic):
- new_topic = add_topic_in_a_forum(forum, profile)
- if number_of_forum == 1 and number_of_topic == 1:
- return new_topic
-
- def create_topic_with_post(self, number_of_post=REST_PAGE_SIZE, profile=None):
- if profile is None:
- profile = ProfileFactory()
-
- category, forum = create_category()
- new_topic = add_topic_in_a_forum(forum, profile)
- posts = []
-
- for post in xrange(0, number_of_post):
- posts.append(PostFactory(topic=new_topic, author=profile.user, position=2))
-
- return new_topic, posts
-
- def test_list_of_forums_empty(self):
- """
- Gets empty list of forums in the database.
- """
- response = self.client.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_forums(self):
- """
- Gets list of forums not empty in the database.
- """
- self.create_multiple_forums()
-
- response = self.client.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_forums_private(self):
- """
- Gets list of private forums not empty in the database (only for staff).
- """
- category, forum = create_category(self.group_staff)
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
-
- response = self.client_authenticated.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
-
- response = self.client_authenticated_staff.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 1) # TODO nombre a affiner en fonction de la realite
-
-
- def test_list_of_forums_with_several_pages(self):
- """
- Gets list of forums with several pages in the database.
- """
- self.create_multiple_forums(REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client.get(reverse('api:forum:list') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 1)
-
- def test_list_of_forums_for_a_page_given(self):
- """
- Gets list of forums with several pages and gets a page different that the first one.
- """
- self.create_multiple_forums(REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 11)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_forums_for_a_wrong_page_given(self):
- """
- Gets an error when the forums asks a wrong page.
- """
- response = self.client.get(reverse('api:forum:list') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_forums_with_a_custom_page_size(self):
- """
- Gets list of forums with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- self.create_multiple_forums(REST_PAGE_SIZE * 2)
-
- page_size = 'page_size'
- response = self.client.get(reverse('api:forum:list') + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_forums_with_a_wrong_custom_page_size(self):
- """
- Gets list of forums with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- self.create_multiple_forums(page_size_value)
-
- response = self.client.get(reverse('api:forum:list') + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), page_size_value)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_details_forum(self):
- """
- Tries to get the details of a forum.
- """
-
- category, forum = create_category()
- response = self.client.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('id'), forum.id)
- self.assertEqual(response.data.get('title'), forum.title)
- self.assertEqual(response.data.get('subtitle'), forum.subtitle)
- self.assertEqual(response.data.get('slug'), forum.slug)
- self.assertEqual(response.data.get('category'), forum.category.id)
- self.assertEqual(response.data.get('position_in_category'), forum.position_in_category)
-
- print('-------')
- print(type(response.data.get('group')))
- print(type(list(forum.group.all())))
- print('-------')
- self.assertEqual(response.data.get('group'), list(forum.group.all()))
-
-
- def test_details_forum_private(self):
- """
- Tries to get the details of a private forum with different users.
- """
- category, forum = create_category(self.group_staff)
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- response = self.client_authenticated_staff.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
- def test_details_unknown_forum(self):
- """
- Tries to get the details of a forum that does not exists.
- """
-
- self.create_multiple_forums(1)
- response = self.client.get(reverse('api:forum:detail', args=[3]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_details_private_forum_user(self):
- """
- Tries to get the details of a private forum with a normal user, staff user and anonymous one.
- """
- category, forum = create_category(self.group_staff)
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- response = self.client_authenticated_staff.get(reverse('api:forum:detail', args=[forum.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
-# TODO
-# Récupérer la liste des sujets en filtrant sur le tag (resulat non vide)
-# Récupérer la liste des sujets en filtrant tag, forum, auteur (resulat non vide)
-
- def test_list_of_topics_empty(self):
- """
- Gets empty list of topics in the database.
- """
- response = self.client.get(reverse('api:forum:list-topic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_topics(self):
- """
- Gets list of topics not empty in the database.
- """
- self.create_multiple_forums_with_topics(1)
- response = self.client.get(reverse('api:forum:list-topic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_topics_with_several_pages(self):
- """
- Gets list of topics with several pages in the database.
- """
- self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list-topic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client.get(reverse('api:forum:list-topic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 1)
-
- def test_list_of_topics_for_a_page_given(self):
- """
- Gets list of topics with several pages and gets a page different that the first one.
- """
- self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list-topic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 11)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_topics_for_a_wrong_page_given(self):
- """
- Gets an error when the topics asks a wrong page.
- """
- response = self.client.get(reverse('api:forum:list-topic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_topics_with_a_custom_page_size(self):
- """
- Gets list of topics with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE * 2)
-
- page_size = 'page_size'
- response = self.client.get(reverse('api:forum:list-topic') + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_topics_with_a_wrong_custom_page_size(self):
- """
- Gets list of topics with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- self.create_multiple_forums_with_topics(1, page_size_value)
-
- response = self.client.get(reverse('api:forum:list-topic') + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), page_size_value)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_list_of_topics_with_forum_filter_empty(self):
- """
- Gets an empty list of topics in a forum.
- """
- self.create_multiple_forums_with_topics(1)
- response = self.client.get(reverse('api:forum:list-topic') + '?forum=3')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_topics_with_forum_filter(self):
- """
- Gets a list of topics in a forum.
- """
- self.create_multiple_forums_with_topics(1)
- forum = Forum.objects.all().first()
- response = self.client.get(reverse('api:forum:list-topic') + '?forum=' + str(forum.id))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- def test_list_of_topics_with_author_filter_empty(self):
- """
- Gets an empty list of topics created by an user.
- """
- self.create_multiple_forums_with_topics(1)
- response = self.client.get(reverse('api:forum:list-topic') + '?author=6')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_topics_with_author_filter(self):
- """
- Gets a list of topics created by an user.
- """
- self.create_multiple_forums_with_topics(1, REST_PAGE_SIZE, self.profile)
- response = self.client.get(reverse('api:forum:list-topic') + '?author=' + str(self.profile.user.id))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- def test_list_of_topics_with_tag_filter_empty(self):
- """
- Gets an empty list of topics with a specific tag.
- """
- self.create_multiple_forums_with_topics(1)
- response = self.client.get(reverse('api:forum:list-topic') + '?tags__title=ilovezozor')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_new_topic_with_user(self):
- """
- Post a new topic in a forum with an user.
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- topic = Topic.objects.filter(author=self.profile.user.id).first()
- self.assertEqual(response.data.get('title'), topic.title)
- self.assertEqual(response.data.get('subtitle'), topic.subtitle)
- self.assertEqual(data.get('text'), topic.last_message.text)
- self.assertEqual(response.data.get('author'), self.profile.user.id)
- self.assertIsNotNone(response.data.get('last_message'))
- self.assertIsNotNone(response.data.get('pubdate'))
-
- def test_new_topic_with_anonymous(self):
- """
- Post a new topic in a forum with an anonymous user.
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
- self.client = APIClient()
- response = self.client.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- def test_new_topic_private_forum(self):
- """
- Post a new topic in a private forum (staff only) with an anonymous user, normal user and staff user.
- """
- profile = ProfileFactory()
- self.group_staff.user_set.add(profile.user)
- category, forum = create_category(self.group_staff)
- data = {
- 'title': 'Have you seen the guy flooding ?',
- 'subtitle': 'He is asking to many question about flask.',
- 'text': 'Should we ban him ? I think we should.',
- 'forum': forum.id
- }
-
- # Anonymous
- self.client = APIClient()
- response = self.client.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # User
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff
- response = self.client_authenticated_staff.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- def test_new_topic_with_banned_user(self):
-
- profile = ProfileFactory()
- profile.can_read = False
- profile.can_write = False
- profile.save()
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_new_topic_without_title(self):
- """
- Try to post a new topic in a forum without the title
- """
- self.create_multiple_forums()
- data = {
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_new_topic_without_subtitle(self):
- """
- Try to post a new topic in a forum without the title
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 1
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- def test_new_topic_without_text(self):
- """
- Try to post a new topic in a forum without the text.
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'forum': 1
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_new_topic_in_unknow_forum(self):
- """
- Try to post a new topic in a forum that does not exists.
- """
- self.create_multiple_forums()
- data = {
- 'title': 'Flask 4 Ever !',
- 'subtitle': 'Is it the best framework ?',
- 'text': 'I head that Flask is the best framework ever, is that true ?',
- 'forum': 666
- }
-
- response = self.client_authenticated.post(reverse('api:forum:list-topic'), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_details_topic(self):
- """
- Get details of a topic.
- """
- topic = self.create_multiple_forums_with_topics(1, 1)
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail-topic', args=(topic.id,)))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('title'), topic.title)
- self.assertEqual(response.data.get('subtitle'), topic.subtitle)
- self.assertEqual(response.data.get('forum'), topic.forum.id)
- self.assertIsNotNone(response.data.get('title'))
- self.assertIsNotNone(response.data.get('forum'))
-
- def test_details_unknown_topic(self):
- """
- Get details of a topic that doesw not exist.
- """
-
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail-topic', args=[666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_details_topic_private(self):
- """
- Tries to get details of a topic that is in a private forum.
- """
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, self.staff)
-
- # Anonymous
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail-topic', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # User
- response = self.client_authenticated.get(reverse('api:forum:detail-topic', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff
- response = self.client_authenticated_staff.get(reverse('api:forum:detail-topic', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
-
-# Édite un sujet qvec user en ls
-# Édite un sujet avec user banni
-# Editer dans un forum privé ? Verifier les auths
-# TODO
-
- def test_update_topic_details_title(self):
- """
- Updates title of a topic.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- print('test_update_topic_details_title')
- print(response)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('title'), data.get('title'))
-
- def test_update_topic_details_title_empty(self):
- """
- Updates title of a topic, tries to put an empty sting
- """
- data = {
- 'title': ''
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_update_topic_details_subtitle(self):
- """
- Updates subtitle of a topic.
- """
- data = {
- 'subtitle': 'Mon nouveau sous-titre'
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('subtitle'), data.get('subtitle'))
-
- def test_update_topic_anonymous(self):
- """
- Tries to update a Topic with an anonymous user.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- topic = self.create_multiple_forums_with_topics(1, 1)
- self.client = APIClient()
- response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- def test_update_topic_staff(self):
- """
- Updates title of a topic with a staff member.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('title'), data.get('title'))
-
- def test_update_topic_other_user(self):
- """
- Tries to update title of a topic posted by another user.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- profile = ProfileFactory()
- topic = self.create_multiple_forums_with_topics(1, 1, profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_update_unknown_topic(self):
- """
- Tries to update title of a non existing topic.
- """
- data = {
- 'title': 'Mon nouveau titre'
- }
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[666]), data)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_update_topic_forum(self):
- """
- Tries to move (change forum in which the topic is) with different users.
- """
- data = {
- 'forum': 5
- }
- profile = ProfileFactory()
- self.create_multiple_forums_with_topics(5, 1, profile)
- topic = Topic.objects.filter(forum=1).first()
-
- # Anonymous
- self.client = APIClient()
- response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # User
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('forum'), data.get('forum'))
-
- def test_update_topic_lock(self):
- """
- Tries to lock a Topic with different users.
- """
- data = {
- 'is_locked': True
- }
- topic = self.create_multiple_forums_with_topics(1, 1)
-
- self.client = APIClient()
- response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(response.data.get('is_locked'))
-
- def test_update_topic_solve(self):
- """
- Tries to solve a Topic with different users.
- """
- data = {
- 'is_solved': True
- }
- topic = self.create_multiple_forums_with_topics(1, 1, self.profile)
-
- self.client = APIClient()
- response = self.client.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # Author
- response = self.client_authenticated.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(response.data.get('is_solved'))
-
- # Other user
- other_profile = ProfileFactory()
- client_oauth2 = create_oauth2_client(other_profile.user)
- client_other_user = APIClient()
- authenticate_client(client_other_user, client_oauth2, other_profile.user.username, 'hostel77')
-
- response = client_other_user.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-topic', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(response.data.get('is_solved'))
-
- def test_list_of_posts_unknown(self):
- """
- Tries to get a list of posts in an unknown topic
- """
- response = self.client.get(reverse('api:forum:list-post', args=[666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_posts(self):
- """
- Gets list of posts in a topic.
- """
- # A post is already included with the topic
- topic, posts = self.create_topic_with_post(REST_PAGE_SIZE - 1)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE)
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_posts_private_forum(self):
- """
- Get a list of posts in a topic of a private forum.
- """
- group = Group.objects.create(name="DummyGroup_1")
-
- profile = ProfileFactory()
- group.user_set.add(profile.user)
- category, forum = create_category(group)
- topic = add_topic_in_a_forum(forum, profile)
- # def add_topic_in_a_forum(forum, profile, is_sticky=False, is_solved=False, is_locked=False):
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 1)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_posts_private_forum_user(self):
- """
- Tries to get a list of posts in a topic of a private forum with a normal user.
- """
- profile = ProfileFactory()
- self.group_staff.user_set.add(profile.user)
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-post', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 1)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_posts_with_several_pages(self):
- """
- Gets list of posts with several pages in the database.
- """
- topic, posts = self.create_topic_with_post(REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 2) # Note : when creating a Topic a first post is created, explaining the +1
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 2)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 2)
-
- def test_list_of_posts_for_a_page_given(self):
- """
- Gets list of posts with several pages and gets a page different that the first one.
- """
- topic, posts = self.create_topic_with_post(REST_PAGE_SIZE + 1)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 12)
- self.assertEqual(len(response.data.get('results')), 2)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_posts_for_a_wrong_page_given(self):
- """
- Gets an error when the posts asks a wrong page.
- """
- topic, posts = self.create_topic_with_post(1)
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_posts_with_a_custom_page_size(self):
- """
- Gets list of forums with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- topic, posts = self.create_topic_with_post((REST_PAGE_SIZE * 2) - 1)
- print (topic)
-
- page_size = 'page_size'
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_posts_in_topic_with_a_wrong_custom_page_size(self):
- """
- Gets list of posts with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- topic, posts = self.create_topic_with_post(page_size_value)
-
- response = self.client.get(reverse('api:forum:list-post', args=[topic.id]) + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
- # We will have page_size_value + 1 because a post is added at topic creation.
- self.assertEqual(response.data.get('count'), page_size_value + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_list_of_posts_in_unknown_topic(self):
- """
- Tries to list the posts of an non existing Topic.
- """
-
- response = self.client.get(reverse('api:forum:list-post', args=[666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_user_topics_empty(self):
- """
- Gets empty list of topic that the user created.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_user_topics(self):
- """
- Gets list of user's topics not empty in the database.
- """
- self.create_multiple_forums_with_topics(10, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 10)
- self.assertEqual(len(response.data.get('results')), 10)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_user_topics_with_several_pages(self):
- """
- Gets list of user's topics with several pages in the database.
- """
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic'))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 1)
-
- def test_list_of_user_topics_for_a_page_given(self):
- """
- Gets list of user's topics with several pages and gets a page different that the first one.
- """
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 11)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_users_topics_for_a_wrong_page_given(self):
- """
- Gets an error when the user's topics asks a wrong page.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_user_topics_with_a_custom_page_size(self):
- """
- Gets list of user's topics with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE * 2, 1, self.profile)
-
- page_size = 'page_size'
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_user_topics_with_a_wrong_custom_page_size(self):
- """
- Gets list of user's topic with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- self.create_multiple_forums_with_topics(page_size_value, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-usertopic') + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), page_size_value)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_list_of_user_topics_anonymous(self):
- """
- Tries to get a list of users topic with an anonymous user.
- """
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:list-usertopic'))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
-
-# DONE Créer un message 200
-# DONE Créer un message avec un contenu vide
-# DONECréer un message dans un sujet qui n'existe pas
-# DONE Créer un message en anonymous
-# DONE Créer un message dans un forum privé en user
-# DONE Créer un message dans un forum privé en staff
-# Créer un message dans un sujet fermé en user
-# Créer un message dans un sujet fermé en staff
-# Créer un message pour tester l'antiflood
-
-
- def test_create_post_with_no_field(self):
- """
- Creates a post in a topic but not with according field.
- """
- topic = self.create_multiple_forums_with_topics(1, 1)
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.id]), {})
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_create_post_with_empty_field(self):
- """
- Creates a post in a topic but with no text.
- """
- data = {
- 'text': ''
- }
- topic = self.create_multiple_forums_with_topics(1, 1)
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-
- def test_create_post_unauthenticated(self):
- """
- Creates a post in a topic with unauthenticated client.
- """
- data = {
- 'text': 'Welcome to this post!'
- }
-
- topic = self.create_multiple_forums_with_topics(1, 1)
- self.client = APIClient()
- with transaction.atomic():
- response = self.client.post(reverse('api:forum:list-post', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- def test_create_post_with_bad_topic_id(self):
- """
- Creates a post in a topic with a bad topic id.
- """
- data = {
- 'text': 'Welcome to this post!'
- }
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[666]), data)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_create_post(self):
- """
- Creates a post in a topic.
- """
- data = {
- 'text': 'Welcome to this post!'
- }
- topic = self.create_multiple_forums_with_topics(1, 1)
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.id]), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- post = Post.objects.filter(topic=topic.id).last()
-
- self.assertEqual(response.data.get('text'), data.get('text'))
- self.assertEqual(response.data.get('text'), post.text)
- self.assertEqual(response.data.get('is_useful'), post.is_useful)
- self.assertEqual(response.data.get('author'), post.author.id)
- self.assertEqual(response.data.get('position'), post.position)
-
- def test_failure_post_in_a_forum_we_cannot_read(self):
- """
- Tries to create a post in a private topic with a normal user.
- """
- print('--------------------------- test rate--------------')
- profile = StaffProfileFactory()
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, profile)
- data = {
- 'text': 'Welcome to this post!'
- }
- response = self.client_authenticated.post(reverse('api:forum:list-post', args=[topic.pk]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_post_in_a_private_forum(self):
- """
- Post in a private topic with a user that has access right.
- """
-
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, self.staff)
- data = {
- 'text': 'Welcome to this post!'
- }
- response = self.client_authenticated_staff.post(reverse('api:forum:list-post', args=[topic.pk,]), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- post = Post.objects.filter(topic=topic.id).last()
- self.assertEqual(response.data.get('text'), data.get('text'))
- self.assertEqual(response.data.get('text'), post.text)
- self.assertEqual(response.data.get('is_useful'), post.is_useful)
- self.assertEqual(response.data.get('author'), post.author.id)
- self.assertEqual(response.data.get('position'), post.position)
-
- def test_new_post_user_with_restrictions(self):
- """
- Try to post a new post with an user that has some restrictions .
- """
-
- # Banned
- profile = ProfileFactory()
- profile.can_read = False
- profile.can_write = False
- profile.save()
- topic = self.create_multiple_forums_with_topics(1, 1)
- data = {
- 'text': 'I head that Flask is the best framework ever, is that true ?'
- }
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.post(reverse('api:forum:list-post', args=[topic.id,]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Read only
- profile = ProfileFactory()
- profile.can_write = False
- profile.save()
-
- self.assertTrue(self.client.login(username=profile.user.username, password='hostel77'))
- response = self.client.post(reverse('api:forum:list-post', args=[topic.id,]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_detail_post(self):
- """
- Gets all information about a post.
- """
-
- topic, posts = self.create_topic_with_post()
- post = posts[0]
- response = self.client.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(post.id, response.data.get('id'))
- self.assertIsNotNone(response.data.get('text'))
- self.assertIsNotNone(response.data.get('text_html'))
- self.assertIsNotNone(response.data.get('pubdate'))
- self.assertIsNone(response.data.get('update'))
- print('test_detail_post')
- print(response.data)
- self.assertEqual(post.position, response.data.get('position'))
- self.assertEqual(topic.author.id, response.data.get('author'))
-
- def test_detail_of_a_private_post_not_present(self):
- """
- Gets an error 404 when the post isn't present in the database.
- """
- topic = self.create_multiple_forums_with_topics(1, 1)
- response = self.client.get(reverse('api:forum:detail-post', args=[topic.id, 666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_detail_of_private_post(self):
- """
- Tries to get all the data about a post in a private topic (and forum) with different users.
- """
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, self.profile)
- post = Post.objects.filter(topic=topic.id).first()
-
- # Anonymous
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # User
- response = self.client_authenticated.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # Staff user
- response = self.client_authenticated_staff.get(reverse('api:forum:detail-post', args=[topic.id, post.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
- def test_list_of_member_posts_empty(self):
- """
- Gets empty list of posts that that a specified member created.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
- self.assertEqual(response.data.get('results'), [])
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_member_posts(self):
- """
- Gets list of a member posts not empty in the database.
- """
- self.create_multiple_forums_with_topics(10, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 10)
- self.assertEqual(len(response.data.get('results')), 10)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
-
- def test_list_of_staff_posts(self):
- """
- Gets list of a staff posts.
- """
-
- profile = StaffProfileFactory()
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, profile)
-
- # Anonymous user cannot see staff private post.
- self.client = APIClient()
- response = self.client.get(reverse('api:forum:list-memberpost', args=[profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
-
- # Same for normal user
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 0)
-
- response = self.client_authenticated_staff.get(reverse('api:forum:list-memberpost', args=[profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 1)
-
- def test_list_of_member_posts_with_several_pages(self):
- """
- Gets list of a member topics with several pages in the database.
- """
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), REST_PAGE_SIZE)
-
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), REST_PAGE_SIZE + 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
- self.assertEqual(len(response.data.get('results')), 1)
-
- def test_list_of_member_posts_for_a_page_given(self):
- """
- Gets list of a member topics with several pages and gets a page different that the first one.
- """
-
- self.create_multiple_forums_with_topics(REST_PAGE_SIZE + 1, 1, self.profile)
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id])+ '?page=2')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 11)
- self.assertEqual(len(response.data.get('results')), 1)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNotNone(response.data.get('previous'))
-
- def test_list_of_member_post_for_a_wrong_page_given(self):
- """
- Gets an error when the member posts asks a wrong page.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?page=2')
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_list_of_member_posts_with_a_custom_page_size(self):
- """
- Gets list of user's posts with a custom page size. DRF allows to specify a custom
- size for the pagination.
- """
- # When a topic is created, a post is also created.
- self.create_topic_with_post((REST_PAGE_SIZE * 2) - 1, self.profile)
-
- page_size = 'page_size'
- response = self.client.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?{}=20'.format(page_size))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), 20)
- self.assertEqual(len(response.data.get('results')), 20)
- self.assertIsNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_PAGE_SIZE_QUERY_PARAM, page_size)
-
- def test_list_of_member_posts_with_a_wrong_custom_page_size(self):
- """
- Gets list of member posts with a custom page size but not good according to the
- value in settings.
- """
- page_size_value = REST_MAX_PAGE_SIZE + 1
- self.create_multiple_forums_with_topics(page_size_value, 1, self.profile)
-
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[self.profile.id]) + '?page_size={}'.format(page_size_value))
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('count'), page_size_value)
- self.assertIsNotNone(response.data.get('next'))
- self.assertIsNone(response.data.get('previous'))
- self.assertEqual(REST_MAX_PAGE_SIZE, len(response.data.get('results')))
-
- def test_list_of_unknow_member_posts(self):
- """
- Gets empty list of posts for a member that does not exists.
- """
- response = self.client_authenticated.get(reverse('api:forum:list-memberpost', args=[666]))
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_alert_post(self):
- """
- Tries to alert a post in a public forum with different type of users
- """
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- another_profile = ProfileFactory()
- post = PostFactory(topic=topic, author=another_profile.user, position=1)
- data = {
- 'text': 'There is a guy flooding about Flask, con you do something about it ?'
- }
-
- self.client = APIClient()
- response = self.client.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- alerte = Alert.objects.latest('pubdate')
- self.assertEqual(alerte.text, data.get('text'))
- self.assertEqual(alerte.author, self.profile.user)
- self.assertEqual(alerte.comment.id, post.id)
-
-
- def test_alert_post_in_private_forum(self):
- """
- Tries to alert a post in a public forum with different type of users
- """
- profile = StaffProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- post = PostFactory(topic=topic, author=profile.user, position=1)
- data = {
- 'text': 'There is a guy flooding about Flask, con you do something about it ?'
- }
-
- self.client = APIClient()
- response = self.client.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- response = self.client_authenticated.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- response = self.client_authenticated_staff.post(reverse('api:forum:alert-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-
- alerte = Alert.objects.latest('pubdate')
- self.assertEqual(alerte.text, data.get('text'))
- self.assertEqual(alerte.author, self.client_authenticated_staff)
- self.assertEqual(alerte.comment, post.id)
-
- def test_alert_post_not_found(self):
- """
- Tries to alert a post in a public forum with different type of users
- """
- profile = ProfileFactory()
- category, forum = create_category()
- topic = add_topic_in_a_forum(forum, profile)
- post = PostFactory(topic=topic, author=profile.user, position=1)
- data = {
- 'text': 'There is a guy flooding about Flask, con you do something about it ?'
- }
-
- response = self.client_authenticated.post(reverse('api:forum:alert-post', args=[topic.id, 666]), data)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
-# Edite un message d'un sujet fermé user
-# Edite un message d'un sujet fermé staff
-
-# TODO
-
-
- def test_update_post_anonymous(self):
- """
- Tries to update a post with anonymous user.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- self.client = APIClient();
- topic, posts = self.create_topic_with_post()
- response = self.client.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- def test_update_post_other_user(self):
- """
- Tries to update a post with another user that the one who posted on the first time.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- another_profile = ProfileFactory()
- topic, posts = self.create_topic_with_post(REST_PAGE_SIZE, another_profile)
- print('test_update_post_other_user')
- response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- def test_update_post(self):
- """
- Updates a post with user.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- topic, posts = self.create_topic_with_post(1, self.profile)
- response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('text'), data.get('text'))
-
- def test_update_post_staff(self):
- """
- Update a post with a staff user.
- """
- data = {
- 'text': 'I am Vladimir Lupin, I do want I want.'
- }
- topic, posts = self.create_topic_with_post()
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-post', args=[topic.id, posts[0].id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(response.data.get('text'), data.get('text'))
-
- def test_update_unknow_post(self):
- """
- Tries to update post that does not exist.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[666, 42]), data)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_update_post_in_private_topic(self):
- """
- Tries to update a post in a private forum (and topic) with anonymous user.
- """
- data = {
- 'text': 'I made an error I want to edit.'
- }
- self.client = APIClient();
- category, forum = create_category(self.group_staff)
- topic = add_topic_in_a_forum(forum, self.staff)
- post = PostFactory(topic=topic, author=self.staff.user, position=1)
-
- # With anonymous
- response = self.client.put(reverse('api:forum:detail-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
-
- # With user
- response = self.client_authenticated.put(reverse('api:forum:detail-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
- # With staff (member of private forum)
- response = self.client_authenticated_staff.put(reverse('api:forum:detail-post', args=[topic.id, post.id]), data)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(data.get('text'), response.data.get('text'))
-
-def create_oauth2_client(user):
- client = Application.objects.create(user=user,
- client_type=Application.CLIENT_CONFIDENTIAL,
- authorization_grant_type=Application.GRANT_PASSWORD)
- client.save()
- return client
-
-
-def authenticate_client(client, client_auth, username, password):
- client.post('/oauth2/token/', {
- 'client_id': client_auth.client_id,
- 'client_secret': client_auth.client_secret,
- 'username': username,
- 'password': password,
- 'grant_type': 'password'
- })
- access_token = AccessToken.objects.get(user__username=username)
- client.credentials(HTTP_AUTHORIZATION='Bearer {}'.format(access_token))
-
-# TODO
-# Reorganiser le code de test en differentes classes, reordonner les tests
-# Gérer le champ update (date) lors de l'edit
-# Gérer l'antispam
-# identifer quand masquer les messages (message modéré ou masqué par son auteur)
-# empecher les ls et les ban de faire des alertes
-# Gestion de tags (post/edit/details)
-# Tests qui ne passent pas
-# Style / PEP8
-
-# TESTS MANQUANTS
-# Vérifier que l'on affiche pas le text hidden ou l'adresse ip
-# Créer un topic avec des tags (ajouter le test), éditer les tags
-# Tester le cas ou un user veux vider le contenu de son message
-# Ajouter le cas ou le staff ou un user masque son message, mais un autre user ne peut pas le faire
-# Poste dans un sujet fermé (3 roles)
From 696bb81a651d9b12b92d07a00346918ecf81fd08 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 22:10:25 +0200
Subject: [PATCH 55/78] Update views.py
---
zds/forum/api/views.py | 646 +----------------------------------------
1 file changed, 5 insertions(+), 641 deletions(-)
diff --git a/zds/forum/api/views.py b/zds/forum/api/views.py
index 378dbe256b..2995d65770 100644
--- a/zds/forum/api/views.py
+++ b/zds/forum/api/views.py
@@ -1,647 +1,11 @@
# coding: utf-8
-from zds.member.api.permissions import CanReadTopic, CanReadPost, CanReadForum, CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, IsOwnerOrReadOnly, IsStaffUser
+from rest_framework.permissions import IsAuthenticatedOrReadOnly
+from zds.member.api.permissions import CanReadTopic, CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly
from zds.utils.api.views import KarmaView
-from zds.forum.models import Post, Forum, Topic, Category
-import datetime
-from django.core.cache import cache
-from django.db.models.signals import post_save, post_delete
-from django.http import Http404
-from rest_framework import filters
-from rest_framework.generics import ListAPIView, ListCreateAPIView, RetrieveUpdateAPIView, RetrieveAPIView, CreateAPIView
-from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor
-from rest_framework_extensions.cache.decorators import cache_response
-from rest_framework_extensions.etag.decorators import etag
-from rest_framework_extensions.key_constructor import bits
-from rest_framework.response import Response
-from rest_framework import status
-from rest_framework.permissions import IsAuthenticatedOrReadOnly, AllowAny, IsAuthenticated
-from dry_rest_permissions.generics import DRYPermissions
-from zds.api.bits import DJRF3xPaginationKeyBit, UpdatedAtKeyBit
-from zds.utils import slugify
-from zds.forum.api.serializer import ForumSerializer, TopicSerializer, TopicCreateSerializer, TopicUpdateSerializer, TopicUpdateStaffSerializer, PostSerializer, PostCreateSerializer, PostUpdateSerializer, AlertSerializer
-from zds.forum.api.permissions import IsStaffUser, IsOwnerOrIsStaff, CanWriteInForum, CanWriteInTopic
-from zds.member.models import User
-from itertools import chain
+from zds.forum.models import Post
-class PostKarmaView(KarmaView):
- queryset = Post.objects.all()
- permission_classes = (IsAuthenticatedOrReadOnly, CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, CanReadPost)
-
-
-class PagingSearchListKeyConstructor(DefaultKeyConstructor):
- pagination = DJRF3xPaginationKeyBit()
- list_sql_query = bits.ListSqlQueryKeyBit()
- unique_view_id = bits.UniqueViewIdKeyBit()
- user = bits.UserKeyBit()
- updated_at = UpdatedAtKeyBit('api_updated_forum')
-
-
-class DetailKeyConstructor(DefaultKeyConstructor):
- format = bits.FormatKeyBit()
- language = bits.LanguageKeyBit()
- retrieve_sql_query = bits.RetrieveSqlQueryKeyBit()
- unique_view_id = bits.UniqueViewIdKeyBit()
- user = bits.UserKeyBit()
- updated_at = UpdatedAtKeyBit('api_updated_forum')
-
-
-def change_api_forum_updated_at(sender=None, instance=None, *args, **kwargs):
- cache.set('forum_updated_tag', datetime.datetime.utcnow())
-
-
-post_save.connect(receiver=change_api_forum_updated_at, sender=Forum)
-post_delete.connect(receiver=change_api_forum_updated_at, sender=Forum)
-
-
-class ForumListAPI(ListCreateAPIView):
- """
- Profile resource to list all forum.
- """
- serializer_class = ForumSerializer
- list_key_func = PagingSearchListKeyConstructor()
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all forum in the system.
- ---
-
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
-
- def get_queryset(self):
- public_forums = Forum.objects.filter(group__isnull=True).order_by('position_in_category')
- private_forums = Forum.objects.filter(group__in=self.request.user.groups.all()).order_by('position_in_category')
- return public_forums | private_forums
-
- def get_permissions(self):
- permission_classes = [CanReadForum] # TODO style plus joli ?
- return [permission() for permission in permission_classes]
-
-
-class ForumDetailAPI(RetrieveAPIView):
- """
- Profile resource to display details of a forum.
- ---
- """
-
- queryset = Forum.objects.all()
- obj_key_func = DetailKeyConstructor()
- serializer_class = ForumSerializer
-
- @etag(obj_key_func)
- @cache_response(key_func=obj_key_func)
- def get(self, request, *args, **kwargs):
- """
- Gets a forum given by its identifier.
- ---
- responseMessages:
- - code: 404
- message: Not Found
- """
- forum = self.get_object()
-
- return self.retrieve(request, *args, **kwargs)
-
- def get_serializer_class(self):
- return ForumSerializer
-
- def get_permissions(self):
- permission_classes = [CanReadForum] # TODO style plus joli ?
- return [permission() for permission in permission_classes]
-
-
-class TopicListAPI(ListCreateAPIView):
- """
- Profile resource to list all topics (GET) or to create a topic (POST)
- """
- queryset = Topic.objects.all()
- filter_backends = (filters.DjangoFilterBackend,)
- filter_fields = ('forum', 'author', 'tags__title')
- list_key_func = PagingSearchListKeyConstructor()
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all topic in a forum.
- ---
-
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- - name: forum
- description: Filters by forum id.
- required: false
- - name: author
- description: Filters by author id.
- required: false
- - name: tags__title
- description: Filters by tag name.
- required: false
- responseMessages:
- - code: 404
- message: Not Found
- """
-
- return self.list(request, *args, **kwargs)
-
- def post(self, request, *args, **kwargs):
- """
- Creates a new topic.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: title
- description: Title of the Topic.
- required: true
- paramType: form
- - name: subtitle
- description: Subtitle of the Topic.
- required: false
- paramType: form
- - name: forum
- description: Identifier of the forum in which the Topic should be posted.
- required: false
- paramType: form
- - name: text
- description: Content of the first post in markdown.
- required: true
- paramType: form
- - name: tags
- description: To add a tag, specify its taf identifier. Specify this parameter
- several times to add several tags.
- required: false
- paramType: form
- responseMessages:
- - code: 400
- message: Bad Request
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Forbidden
- """
- author = request.user.id
- serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
- serializer.is_valid(raise_exception=True)
- serializer.save(author_id=author)
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
-
- def get_serializer_class(self):
- if self.request.method == 'GET':
- return TopicSerializer
- elif self.request.method == 'POST':
- return TopicCreateSerializer
-
- def get_permissions(self):
- permission_classes = [CanReadForum]
- if self.request.method == 'POST':
- print('requete post')
-
- #forum = Forum.objects.get(id=self.request.data.get('forum'))
- #self.check_object_permissions(self.request, forum)
- #permission_classes.append(CanReadAndWriteNowOrReadOnly)
- permission_classes.append(CanWriteInForum)
- permission_classes.append(CanReadAndWriteNowOrReadOnly)
- return [permission() for permission in permission_classes]
-
-
-class UserTopicListAPI(ListAPIView):
- """
- Profile resource to list all topics from current user
- """
-
- serializer_class = TopicSerializer
- filter_backends = (filters.DjangoFilterBackend,)
- filter_fields = ('forum', 'tags__title')
- list_key_func = PagingSearchListKeyConstructor()
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all topic from current user.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 401
- message: Not Authenticated
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
-
- def get_queryset(self):
- topics = Topic.objects.filter(author=self.request.user)
- return topics
-
- def get_permissions(self):
- permission_classes = [AllowAny, IsAuthenticated]
- return [permission() for permission in permission_classes]
-
-
-class TopicDetailAPI(RetrieveUpdateAPIView):
- """
- Profile resource to display and update details of a given topic
- """
- queryset = Topic.objects.all()
- obj_key_func = DetailKeyConstructor()
-
- @etag(obj_key_func)
- @cache_response(key_func=obj_key_func)
- def get(self, request, *args, **kwargs):
- """
- Gets a topic given by its identifier.
- ---
- responseMessages:
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Forbidden
- - code: 404
- message: Not Found
- """
- topic = self.get_object()
-
- return self.retrieve(request, *args, **kwargs)
-
- def put(self, request, *args, **kwargs):
- """
- Updates a topic. Said post must be owned by the authenticated member.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: title
- description: Title of the Topic.
- required: false
- paramType: form
- - name: subtitle
- description: Subtitle of the Topic.
- required: false
- paramType: form
- - name: text
- description: Content of the first post in markdown.
- required: false
- paramType: form
- - name: tags
- description: To add a tag, specify its taf identifier. Specify this parameter
- several times to add several tags.
- required: false
- paramType: form
- responseMessages:
- - code: 400
- message: Bad Request if you specify a bad identifier
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- return self.update(request, *args, **kwargs)
-
- def get_serializer_class(self):
- if self.request.method == 'GET':
- return TopicSerializer
- elif self.request.method == 'PUT':
- if self.request.user.has_perm("forum.change_topic"):
- return TopicUpdateStaffSerializer
- else:
- return TopicUpdateSerializer
-
- def get_permissions(self):
- permission_classes = []
- if self.request.method == 'GET':
- permission_classes.append(CanReadTopic)
- elif self.request.method == 'PUT':
- print(self.request.user)
- permission_classes.append(IsOwnerOrIsStaff)
- permission_classes.append(CanReadTopic)
- return [permission() for permission in permission_classes]
-
-
-class PostListAPI(ListCreateAPIView):
- """
- Profile resource to list all messages in a topic
- """
- list_key_func = PagingSearchListKeyConstructor()
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all posts in a topic
- ---
-
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
- # TODO si message cache ? Le cacher dans l'API
-
- def post(self, request, *args, **kwargs):
- """
- Creates a new post in a topic.
- ---
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: text
- description: Content of the post in markdown.
- required: true
- paramType: form
- - name: text
- description: Content of the first post in markdown.
- required: true
- responseMessages:
- - code: 400
- message: Bad Request
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- author = request.user.id
- topic = self.kwargs.get('pk')
-
- serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
- serializer.is_valid(raise_exception=True)
- serializer.save(position=0, author_id=author, topic_id=topic)
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
-
- def get_serializer_class(self):
- if self.request.method == 'GET':
- return PostSerializer
- elif self.request.method == 'POST':
- return PostCreateSerializer
-
- def get_queryset(self):
- if self.request.method == 'GET':
- posts = Post.objects.filter(topic=self.kwargs.get('pk'))
- if posts.count() == 0:
- raise Http404("Topic with pk {} was not found".format(self.kwargs.get('pk')))
- return posts
-
- def get_current_user(self):
- return self.request.user.profile
-
- def get_permissions(self):
- permission_classes = [CanReadPost]
- if self.request.method == 'POST':
- permission_classes.append(CanReadAndWriteNowOrReadOnly)
- permission_classes.append(CanWriteInTopic)
-
- return [permission() for permission in permission_classes]
-
-
-class MemberPostListAPI(ListAPIView):
- """
- Profile resource to list all posts from a member
- """
- list_key_func = PagingSearchListKeyConstructor()
- serializer_class = PostSerializer
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all posts from a member
- ---
-
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
- # TODO fonctionne mais error xml sur certains post http://zds-anto59290.c9users.io/api/forums/membres/3/sujets
-
- def get_queryset(self):
- if self.request.method == 'GET':
- try:
- author = User.objects.get(pk = self.kwargs.get('pk'))
- except User.DoesNotExist:
- raise Http404("User with pk {} was not found".format(self.kwargs.get('pk')))
-
- # Gets every post of author visible by current user
- posts = Post.objects.get_all_messages_of_a_user(self.request.user, author)
- return posts
-
-
-class UserPostListAPI(ListAPIView):
- """
- Profile resource to list all message from current user
- """
-
- list_key_func = PagingSearchListKeyConstructor()
- serializer_class = PostSerializer
-
- @etag(list_key_func)
- @cache_response(key_func=list_key_func)
- def get(self, request, *args, **kwargs):
- """
- Lists all posts from a current user.
- ---
- parameters:
- - name: page
- description: Restricts output to the given page number.
- required: false
- paramType: query
- - name: page_size
- description: Sets the number of forum per page.
- required: false
- paramType: query
- responseMessages:
- - code: 404
- message: Not Found
- """
- return self.list(request, *args, **kwargs)
-
- def get_queryset(self):
- if self.request.method == 'GET':
- posts = Post.objects.filter(author=self.request.user)
- return posts
-
-
-class PostDetailAPI(RetrieveUpdateAPIView):
- """
- Profile resource to display details of given post
- """
+class PostKarmaView(KarmaView):
queryset = Post.objects.all()
- obj_key_func = DetailKeyConstructor()
-
- @etag(obj_key_func)
- @cache_response(key_func=obj_key_func)
- def get(self, request, *args, **kwargs):
- """
- Gets a post given by its identifier.
- ---
- responseMessages:
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- post = self.get_object()
-
- return self.retrieve(request, *args, **kwargs)
-
- def put(self, request, *args, **kwargs):
- """
- Updates a post. Said post must be owned by the authenticated member.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: text
- description: Content of the post in markdown.
- required: true
- paramType: form
- responseMessages:
- - code: 400
- message: Bad Request if you specify a bad identifier
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- return self.update(request, *args, **kwargs)
-
- def get_serializer_class(self):
- if self.request.method == 'GET':
- return PostSerializer
- elif self.request.method == 'PUT':
- return PostUpdateSerializer
-
- def get_permissions(self):
- permission_classes = [AllowAny, CanReadPost]
- if self.request.method == 'PUT':
- permission_classes.append(IsOwnerOrIsStaff)
- permission_classes.append(CanReadAndWriteNowOrReadOnly)
- return [permission() for permission in permission_classes]
-
-
-class PostAlertAPI(CreateAPIView):
- """
- Alert a topic post to the staff.
- """
-
- def post(self, request, *args, **kwargs):
- """
- Alert a topic post to the staff.
- ---
-
- parameters:
- - name: Authorization
- description: Bearer token to make an authenticated request.
- required: true
- paramType: header
- - name: text
- description: Content of the alert in markdown.
- required: true
- paramType: form
- responseMessages:
- - code: 400
- message: Bad Request
- - code: 401
- message: Not Authenticated
- - code: 403
- message: Permission Denied
- - code: 404
- message: Not Found
- """
- author = request.user
- try:
- post = Post.objects.get(id = self.kwargs.get('pk'))
- except Post.DoesNotExist:
- raise Http404("Post with pk {} was not found".format(self.kwargs.get('pk')))
-
- serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
- serializer.is_valid(raise_exception=True)
- serializer.save(comment=post, pubdate = datetime.datetime.now(), author = author)
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
-
- def get_permissions(self):
- permission_classes = [CanReadPost, IsAuthenticated]
- return [permission() for permission in permission_classes]
-
- def get_serializer_class(self):
- return AlertSerializer
+ permission_classes = (IsAuthenticatedOrReadOnly, CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, CanReadTopic)
From 868f7c3539423c50909b6f132b1f91469b996341 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Wed, 10 May 2017 22:10:35 +0200
Subject: [PATCH 56/78] Update
From ef875a5d808ca1832b5c4d041d881041173bacd4 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Thu, 11 May 2017 13:08:13 +0200
Subject: [PATCH 57/78] Update urls.py
---
zds/forum/api/urls.py | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/zds/forum/api/urls.py b/zds/forum/api/urls.py
index a30fdb4665..5ad9d5aad5 100644
--- a/zds/forum/api/urls.py
+++ b/zds/forum/api/urls.py
@@ -1,16 +1,6 @@
from django.conf.urls import url
-from .views import PostKarmaView, ForumListAPI, ForumDetailAPI, PostListAPI, TopicListAPI, TopicDetailAPI, UserTopicListAPI, MemberPostListAPI, UserPostListAPI, PostDetailAPI, PostAlertAPI
+from .views import from .views import PostKarmaView
urlpatterns = [
- url(r'^$', ForumListAPI.as_view(), name='list'),
- url(r'^(?P[0-9]+)/?$', ForumDetailAPI.as_view(), name='detail'),
- url(r'^sujets/?$', TopicListAPI.as_view(), name='list-topic'),
- url(r'^membre/sujets/?$', UserTopicListAPI.as_view(), name='list-usertopic'),
url(r'^message/(?P\d+)/karma/?$', PostKarmaView.as_view(), name='post-karma'),
- url(r'^sujets/(?P[0-9]+)/messages?$', PostListAPI.as_view(), name='list-post'),
- url(r'^sujets/(?P[0-9]+)/?$', TopicDetailAPI.as_view(), name='detail-topic'),
- url(r'^membres/(?P[0-9]+)/messages/?$', MemberPostListAPI.as_view(), name='list-memberpost'),
- url(r'^membres/messages/?$', UserPostListAPI.as_view(), name='list-userpost'),
- url(r'^sujets/(?P[0-9]+)/messages/(?P[0-9]+)/?$', PostDetailAPI.as_view(), name='detail-post'),
- url(r'^sujets/(?P[0-9]+)/messages/(?P[0-9]+)/alert/?$', PostAlertAPI.as_view(), name='alert-post')
]
From dc5c9e49b89cfa6549b7a3d8a9a7f8a2719e9250 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Thu, 11 May 2017 13:08:43 +0200
Subject: [PATCH 58/78] Update permissions.py
---
zds/member/api/permissions.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zds/member/api/permissions.py b/zds/member/api/permissions.py
index 2fb1e31199..06bc391e22 100644
--- a/zds/member/api/permissions.py
+++ b/zds/member/api/permissions.py
@@ -86,4 +86,4 @@ class CanReadTopic(permissions.BasePermission):
"""
def has_object_permission(self, request, view, obj):
- return obj.forum.can_read(request.user)
+ return obj.topic.forum.can_read(request.user)
From 9b947371b46879808ef9864fd65f5f9b62c92049 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Thu, 11 May 2017 13:09:07 +0200
Subject: [PATCH 59/78] Update forms.py
---
zds/tutorialv2/forms.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index a60f8ca82a..72f97b8db7 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -16,7 +16,7 @@
from django.utils.translation import ugettext_lazy as _
from zds.member.models import Profile
from zds.tutorialv2.utils import slugify_raise_on_invalid, InvalidSlugError
-from zds.utils.validators import TagValidator
+from zds.utils.forms import TagValidator
class FormWithTitle(forms.Form):
From 0d194468fded8a997c494f2e879ba796ec702f5b Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Thu, 11 May 2017 13:09:37 +0200
Subject: [PATCH 60/78] Update models.py
---
zds/forum/models.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/zds/forum/models.py b/zds/forum/models.py
index 16d7a8a9af..03a15a2fdb 100644
--- a/zds/forum/models.py
+++ b/zds/forum/models.py
@@ -588,6 +588,7 @@ def __str__(self):
self.user,
self.post.pk)
+
def is_read(topic, user=None):
"""
Checks if the user has read the **last post** of the topic.
From c0d07af7273cfba58006cdd4a71bc22a5de5be84 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Sat, 13 May 2017 11:00:44 +0200
Subject: [PATCH 61/78] Update urls.py
---
zds/forum/api/urls.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/zds/forum/api/urls.py b/zds/forum/api/urls.py
index 5ad9d5aad5..76bdf2862c 100644
--- a/zds/forum/api/urls.py
+++ b/zds/forum/api/urls.py
@@ -1,6 +1,7 @@
from django.conf.urls import url
-from .views import from .views import PostKarmaView
+from .views import PostKarmaView
+
urlpatterns = [
url(r'^message/(?P\d+)/karma/?$', PostKarmaView.as_view(), name='post-karma'),
]
From 39ead6edd48b6c955c87e35891e39cd612ccd011 Mon Sep 17 00:00:00 2001
From: Antonin
Date: Wed, 31 May 2017 18:47:48 +0000
Subject: [PATCH 62/78] Remove old code
---
zds/tutorialv2/forms.py | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 72f97b8db7..29a2f40de2 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -319,18 +319,6 @@ def _create_layout(self, hide_help, **kwargs):
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
with text=form.conclusion.value %}{% endif %}'),
- self.helper.layout.append(Layout(
- Field('last_hash'),
- Field('licence'),
- Field('subcategory', template='crispy/checkboxselectmultiple.html'),
- HTML(_(u"Demander de l'aide à la communauté !
"
- u"Si vous avez besoin d'un coup de main, "
- u"sélectionnez une ou plusieurs catégories d'aide ci-dessous "
- u'et votre contenu apparaîtra alors sur la page d\'aide.
')),
- Field('helps'),
-
Field('msg_commit'),
ButtonHolder(
StrictButton('Valider', type='submit'),
From b4567b8c9a25a08c21f45a2d657c6129f11abd12 Mon Sep 17 00:00:00 2001
From: Antonin
Date: Thu, 1 Jun 2017 19:48:41 +0000
Subject: [PATCH 63/78] Peping
---
zds/tutorialv2/forms.py | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 29a2f40de2..428740363d 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -177,21 +177,21 @@ def __init__(self, *args, **kwargs):
self.helper.layout.append(Layout(HTML('')))
self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
- css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
+ ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
+ css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
else:
self.helper.layout.append(Layout(
Field('introduction', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
- css_class='btn btn-grey preview-btn'),),
+ css_class='btn btn-grey preview-btn'),),
HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
- with text=form.introduction.value %}{% endif %}'),
+ with text=form.introduction.value %}{% endif %}'),
Field('conclusion', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),)))
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
- with text=form.conclusion.value %}{% endif %}'),
+ with text=form.conclusion.value %}{% endif %}'),
self.helper.layout.append(Layout(
Field('msg_commit'),
@@ -303,27 +303,27 @@ def _create_layout(self, hide_help, **kwargs):
self.helper.layout.append(Layout(
ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
- css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
+ css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
else:
self.helper.layout.append(Layout(
- Field('introduction', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
- css_class='btn btn-grey preview-btn'),),
- HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
- with text=form.introduction.value %}{% endif %}'),
- Field('conclusion', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
- css_class='btn btn-grey preview-btn'),)))
+ Field('introduction', css_class='md-editor preview-source'),
+ ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ css_class='btn btn-grey preview-btn'),),
+ HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
+ with text=form.introduction.value %}{% endif %}'),
+ Field('conclusion', css_class='md-editor preview-source'),
+ ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ css_class='btn btn-grey preview-btn'),)))
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
- with text=form.conclusion.value %}{% endif %}'),
+ with text=form.conclusion.value %}{% endif %}'),
Field('msg_commit'),
ButtonHolder(
StrictButton('Valider', type='submit'),
),
- ))
+ ))
if not hide_help:
self.helper.layout.append(html_part)
From d00b293fb51e72de2c0b7fe441388d5f9098e84f Mon Sep 17 00:00:00 2001
From: Antonin
Date: Thu, 1 Jun 2017 20:11:24 +0000
Subject: [PATCH 64/78] Style
---
zds/tutorialv2/forms.py | 22 +++++++++++++---------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 428740363d..a91a497bf5 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -190,18 +190,22 @@ def __init__(self, *args, **kwargs):
Field('conclusion', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),)))
+
+
+ self.helper.layout = Layout(
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
with text=form.conclusion.value %}{% endif %}'),
- self.helper.layout.append(Layout(
- Field('msg_commit'),
- Field('last_hash'),
- ButtonHolder(
- StrictButton(
- _(u'Valider'),
- type='submit'),
+ self.helper.layout.append(Layout(
+ Field('msg_commit'),
+ Field('last_hash'),
+ ButtonHolder(
+ StrictButton(
+ _(u'Valider'),
+ type='submit'),
+ )
+ )),
)
- ))
class ContentForm(ContainerForm):
@@ -317,7 +321,7 @@ def _create_layout(self, hide_help, **kwargs):
css_class='btn btn-grey preview-btn'),)))
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
- with text=form.conclusion.value %}{% endif %}'),
+ with text=form.conclusion.value %}{% endif %}')
Field('msg_commit'),
ButtonHolder(
From c80d54679431657c70327f028ab32b3305d7260b Mon Sep 17 00:00:00 2001
From: Antonin
Date: Fri, 2 Jun 2017 17:31:44 +0000
Subject: [PATCH 65/78] fix tests
---
zds/tutorialv2/forms.py | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index a91a497bf5..35018c3b57 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -165,7 +165,7 @@ def __init__(self, *args, **kwargs):
self.helper.layout.append(Layout(
ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
- css_class='btn btn-submit merge-btn need-to-merge-introduction'))))
+ css_class='btn btn-submit merge-btn need-to-merge-introduction'))))
old_conclusion = kwargs.get('data').get('conclusion')
if old_conclusion is None:
@@ -190,8 +190,7 @@ def __init__(self, *args, **kwargs):
Field('conclusion', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),)))
-
-
+
self.helper.layout = Layout(
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
with text=form.conclusion.value %}{% endif %}'),
@@ -299,7 +298,7 @@ def _create_layout(self, hide_help, **kwargs):
old_conclusion = kwargs.get('data').get('conclusion')
if old_conclusion is None:
old_conclusion = ''
-
+
self.helper.layout.append(Layout(Field('conclusion', css_class='hidden')))
self.helper.layout.append(Layout(HTML('' + old_conclusion +
'
')))
@@ -320,14 +319,20 @@ def _create_layout(self, hide_help, **kwargs):
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),)))
+
+ self.helper.layout = Layout(
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
- with text=form.conclusion.value %}{% endif %}')
+ with text=form.conclusion.value %}{% endif %}'),
- Field('msg_commit'),
- ButtonHolder(
- StrictButton('Valider', type='submit'),
- ),
- ))
+ self.helper.layout.append(Layout(
+ Field('msg_commit'),
+ ButtonHolder(
+ StrictButton(
+ _(u'Valider'),
+ type='submit'),
+ )
+ )),
+ )
if not hide_help:
self.helper.layout.append(html_part)
From 63075aa61408a07bc047469a38d03d07a8d0d04c Mon Sep 17 00:00:00 2001
From: Antonin
Date: Sat, 3 Jun 2017 09:00:51 +0000
Subject: [PATCH 66/78] Just PEPing
---
zds/tutorialv2/forms.py | 41 ++++++++++++++++++++---------------------
1 file changed, 20 insertions(+), 21 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 35018c3b57..c2227487f9 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -182,14 +182,14 @@ def __init__(self, *args, **kwargs):
else:
self.helper.layout.append(Layout(
- Field('introduction', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
- css_class='btn btn-grey preview-btn'),),
- HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
+ Field('introduction', css_class='md-editor preview-source'),
+ ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ css_class='btn btn-grey preview-btn'),),
+ HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
with text=form.introduction.value %}{% endif %}'),
- Field('conclusion', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
- css_class='btn btn-grey preview-btn'),)))
+ Field('conclusion', css_class='md-editor preview-source'),
+ ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ css_class='btn btn-grey preview-btn'),)))
self.helper.layout = Layout(
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
@@ -204,7 +204,7 @@ def __init__(self, *args, **kwargs):
type='submit'),
)
)),
- )
+ )
class ContentForm(ContainerForm):
@@ -292,8 +292,8 @@ def _create_layout(self, hide_help, **kwargs):
self.helper.layout.append(Layout(HTML('')))
self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
- css_class='btn btn-submit merge-btn need-to-merge-introduction'))))
+ ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
+ css_class='btn btn-submit merge-btn need-to-merge-introduction'))))
old_conclusion = kwargs.get('data').get('conclusion')
if old_conclusion is None:
@@ -305,20 +305,19 @@ def _create_layout(self, hide_help, **kwargs):
self.helper.layout.append(Layout(HTML('')))
self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
- css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
+ ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
+ css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
else:
self.helper.layout.append(Layout(
- Field('introduction', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
- css_class='btn btn-grey preview-btn'),),
- HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
+ Field('introduction', css_class='md-editor preview-source'),
+ ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ css_class='btn btn-grey preview-btn'),),
+ HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
with text=form.introduction.value %}{% endif %}'),
- Field('conclusion', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
- css_class='btn btn-grey preview-btn'),)))
-
+ Field('conclusion', css_class='md-editor preview-source'),
+ ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ css_class='btn btn-grey preview-btn'),)))
self.helper.layout = Layout(
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
@@ -332,7 +331,7 @@ def _create_layout(self, hide_help, **kwargs):
type='submit'),
)
)),
- )
+ )
if not hide_help:
self.helper.layout.append(html_part)
From fba3423c42164656a2f128524f9b2e32165da06a Mon Sep 17 00:00:00 2001
From: Antonin
Date: Sat, 3 Jun 2017 09:27:37 +0000
Subject: [PATCH 67/78] Just PEPing so more forms
---
zds/utils/forms.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/zds/utils/forms.py b/zds/utils/forms.py
index bcbf45a46f..c3a3cd1dbc 100644
--- a/zds/utils/forms.py
+++ b/zds/utils/forms.py
@@ -42,17 +42,17 @@ def __init__(self, *args, **kwargs):
if kwargs.get('data', None) is not None:
old_text = kwargs.get('data').get('text')
-
+
text_field = Field('text', css_class='hidden')
text_field += HTML('' + old_text + '
')
text_field += HTML('')
-
- text_field += ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge', \
- css_class='btn btn-submit merge-btn need-to-merge-text'))
+
+ text_field += ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
+ css_class='btn btn-submit merge-btn need-to-merge-text'))
else:
text_field = Field('text', css_class='md-editor')
-
+
super(CommonLayoutVersionEditor, self).__init__(
Div(
text_field,
From 5863c8c63b91101cc3ab9d4b9e51cfb12ba908cb Mon Sep 17 00:00:00 2001
From: Antonin
Date: Tue, 22 Aug 2017 21:28:57 +0200
Subject: [PATCH 68/78] Refacto
---
Gulpfile.js | 2 --
assets/js/auto-merge.js | 5 ---
package.json | 1 +
yarn.lock | 13 +++++++-
zds/tutorialv2/forms.py | 71 ++++++++++++++++++-----------------------
5 files changed, 44 insertions(+), 48 deletions(-)
diff --git a/Gulpfile.js b/Gulpfile.js
index 7eae4035eb..9a7e3e4238 100644
--- a/Gulpfile.js
+++ b/Gulpfile.js
@@ -50,8 +50,6 @@ gulp.task('js', () =>
require.resolve('cookies-eu-banner'),
require.resolve('codemirror'),
require.resolve('mergely'),
- 'assets/js/_custom.modernizr.js',
-
// Used by other scripts, must be first
'assets/js/modal.js',
'assets/js/tooltips.js',
diff --git a/assets/js/auto-merge.js b/assets/js/auto-merge.js
index bb909c693e..5d7e9f2203 100644
--- a/assets/js/auto-merge.js
+++ b/assets/js/auto-merge.js
@@ -33,7 +33,6 @@
$("#compare-editor-lhs").append("Votre Version");
$("#compare-editor-rhs").append("La version courante");
-
/**
* Merge content
*/
@@ -65,12 +64,8 @@
setTimeout(function() {
$(".alert-merge").fadeOut("fast");
}, 2000);
-
}
-
});
-
});
-
});
})(jQuery);
diff --git a/package.json b/package.json
index 63ee325586..7e7b732288 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"homepage": "https://github.com/zestedesavoir/zds-site",
"dependencies": {
"autoprefixer": "7.1.2",
+ "codemirror": "^5.28.0",
"cookies-eu-banner": "1.2.8",
"cssnano": "3.10.0",
"del": "3.0.0",
diff --git a/yarn.lock b/yarn.lock
index a3fd4b25a9..2aae4929fd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -535,6 +535,10 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+codemirror@^5.10.0, codemirror@^5.28.0:
+ version "5.28.0"
+ resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.28.0.tgz#2978d9280d671351a4f5737d06bbd681a0fd6f83"
+
color-convert@^1.3.0, color-convert@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
@@ -2333,7 +2337,7 @@ jpegtran-bin@^3.0.0:
bin-wrapper "^3.0.0"
logalot "^2.0.0"
-jquery@3.2.1:
+jquery@3.2.1, jquery@>=1.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787"
@@ -2774,6 +2778,13 @@ merge-stream@^1.0.0:
dependencies:
readable-stream "^2.0.1"
+mergely@3.4.4:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/mergely/-/mergely-3.4.0.tgz#c48d295ace372ac7e40743b590dfb301f206e918"
+ dependencies:
+ codemirror "^5.10.0"
+ jquery ">=1.1.0"
+
micromatch@^2.3.7:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 2182ba493f..e142253043 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -50,6 +50,26 @@ def clean(self):
return cleaned_data
+class MergeableFieldMixin():
+
+ def add_merge_interface_to_field(self, field_name, **kwargs):
+
+ field_old_content = kwargs.get('data').get(field_name)
+ if field_old_content is None:
+ field_old_content = ''
+ self.helper.layout.append(Layout(Field(field_name, css_class='hidden')))
+
+ self.helper.layout.append(
+ Layout(HTML('{1}
'
+ .format(field_name, field_old_content))))
+ self.helper.layout.append(
+ Layout(HTML(''
+ .format(field_name))))
+ self.helper.layout.append(Layout(
+ ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
+ css_class='btn btn-submit merge-btn need-to-merge-{0}'.format(field_name)))))
+
+
class AuthorForm(forms.Form):
username = forms.CharField(
@@ -207,7 +227,7 @@ def __init__(self, *args, **kwargs):
)
-class ContentForm(ContainerForm):
+class ContentForm(ContainerForm, MergeableFieldMixin):
description = forms.CharField(
label=_(u'Description'),
@@ -281,35 +301,12 @@ def _create_layout(self, hide_help, **kwargs):
Field('type'),
Field('image'))
- if kwargs.get('data', None) is not None:
- old_intro = kwargs.get('data').get('introduction')
- if old_intro is None:
- old_intro = ''
-
- self.helper.layout.append(Layout(Field('introduction', css_class='hidden')))
- self.helper.layout.append(Layout(HTML('' +
- old_intro + '
')))
- self.helper.layout.append(Layout(HTML('')))
-
- self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
- css_class='btn btn-submit merge-btn need-to-merge-introduction'))))
-
- old_conclusion = kwargs.get('data').get('conclusion')
- if old_conclusion is None:
- old_conclusion = ''
-
- self.helper.layout.append(Layout(Field('conclusion', css_class='hidden')))
- self.helper.layout.append(Layout(HTML('' + old_conclusion +
- '
')))
- self.helper.layout.append(Layout(HTML('')))
-
- self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
- css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
+ if kwargs.get('data') is not None:
+ print('on ajoute les champs de merge')
+ self.add_merge_interface_to_field('introduction', **kwargs)
+ self.add_merge_interface_to_field('conclusion', **kwargs)
else:
-
- self.helper.layout.append(Layout(
+ self.helper.layout.append(Layout(
Field('introduction', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),),
@@ -319,19 +316,13 @@ def _create_layout(self, hide_help, **kwargs):
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),)))
- self.helper.layout = Layout(
+ self.helper.layout.append(Layout(
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
with text=form.conclusion.value %}{% endif %}'),
-
- self.helper.layout.append(Layout(
- Field('msg_commit'),
- ButtonHolder(
- StrictButton(
- _(u'Valider'),
- type='submit'),
- )
- )),
- )
+ Field('last_hash'),
+ Field('licence'),
+ Field('subcategory', template='crispy/checkboxselectmultiple.html'),
+ ))
if not hide_help:
self.helper.layout.append(html_part)
From 2602d099913e0e4fbe52c1b73d5c9020fa5365fd Mon Sep 17 00:00:00 2001
From: Antonin
Date: Sun, 27 Aug 2017 10:29:30 +0200
Subject: [PATCH 69/78] I hate Js
---
Gulpfile.js | 3 +-
assets/js/auto-merge.js | 116 ++++++++++++++++++++--------------------
assets/scss/main.scss | 1 -
package.json | 1 -
4 files changed, 60 insertions(+), 61 deletions(-)
diff --git a/Gulpfile.js b/Gulpfile.js
index 9a7e3e4238..190f7df0e1 100644
--- a/Gulpfile.js
+++ b/Gulpfile.js
@@ -49,8 +49,9 @@ gulp.task('js', () =>
require.resolve('jquery'),
require.resolve('cookies-eu-banner'),
require.resolve('codemirror'),
- require.resolve('mergely'),
+ //require('codemirror/addon/merge/merge'),
// Used by other scripts, must be first
+ '../node_modules/codemirror/addon/merge/merge.js',
'assets/js/modal.js',
'assets/js/tooltips.js',
diff --git a/assets/js/auto-merge.js b/assets/js/auto-merge.js
index 5d7e9f2203..d61c56c933 100644
--- a/assets/js/auto-merge.js
+++ b/assets/js/auto-merge.js
@@ -1,71 +1,71 @@
-(function($, undefined) {
- "use strict";
+//(function($, undefined) {
+ //"use strict";
- $(document).ready(function () {
+ //$(document).ready(function () {
- /**
- * Sets up the merge interface (using mergely) in the $div Object. Data is generally retrieved from a form field
- * or an aditionnal div exposing the old data, also generated in the form.
- * @param {Object} $div - The base object used to set up the interface. Generally created in forms files.
- * @param {Object} $left - The object from which we will pick the content to put in the left hand side (lhs) of the editor.
- * @param {Object} $right - The object from which we will pick the content to put in the right hand side (rhs) of the editor.
- */
- function mergelySetUp($div, $left, $right)
- {
- $div.mergely({
- width: "auto",
- height: 400,
- sidebar: false,
- cmsettings: { readOnly: false, lineNumbers: true, lineWrapping: true },
- lhs: function(setValue) {
- setValue($left.html());
- },
- rhs: function(setValue) {
- setValue($right.html());
- }
- });
- }
+ ///**
+ //* Sets up the merge interface (using mergely) in the $div Object. Data is generally retrieved from a form field
+ //* or an aditionnal div exposing the old data, also generated in the form.
+ //* @param {Object} $div - The base object used to set up the interface. Generally created in forms files.
+ //* @param {Object} $left - The object from which we will pick the content to put in the left hand side (lhs) of the editor.
+ //* @param {Object} $right - The object from which we will pick the content to put in the right hand side (rhs) of the editor.
+ //*/
+ //function mergelySetUp($div, $left, $right)
+ //{
+ //$div.mergely({
+ //width: "auto",
+ //height: 400,
+ //sidebar: false,
+ //cmsettings: { readOnly: false, lineNumbers: true, lineWrapping: true },
+ //lhs: function(setValue) {
+ //setValue($left.html());
+ //},
+ //rhs: function(setValue) {
+ //setValue($right.html());
+ //}
+ //});
+ //}
- mergelySetUp($(".compare-introduction"),$("#your_introduction"),$("#id_introduction"));
- mergelySetUp($(".compare-conclusion"),$("#your_conclusion"),$("#id_conclusion"));
- mergelySetUp($(".compare-text"),$("#your_text"),$("#id_text"));
+ //mergelySetUp($(".compare-introduction"),$("#your_introduction"),$("#id_introduction"));
+ //mergelySetUp($(".compare-conclusion"),$("#your_conclusion"),$("#id_conclusion"));
+ //mergelySetUp($(".compare-text"),$("#your_text"),$("#id_text"));
- $("#compare-editor-lhs").append("Votre Version");
- $("#compare-editor-rhs").append("La version courante");
+ //$("#compare-editor-lhs").append("Votre Version");
+ //$("#compare-editor-rhs").append("La version courante");
- /**
- * Merge content
- */
- $(".merge-btn").on("click", function(e){
+ ///**
+ //* Merge content
+ //*/
+ //$(".merge-btn").on("click", function(e){
- e.stopPropagation();
- e.preventDefault();
+ //e.stopPropagation();
+ //e.preventDefault();
- var button = $(this);
+ //var button = $(this);
- Array.from(this.classList).forEach(function(element){
- if (element.indexOf("need-to-merge-") >= 0) {
+ //Array.from(this.classList).forEach(function(element){
+ //if (element.indexOf("need-to-merge-") >= 0) {
- // Cut the string to get the ending part
- var substring = element.substring(14);
+ //// Cut the string to get the ending part
+ //var substring = element.substring(14);
- var $intro = $("#id_" + substring);
- var $toMerge = $(".compare-" + substring).mergely("get","rhs");
- $intro.val($toMerge);
+ //var $intro = $("#id_" + substring);
+ //var $toMerge = $(".compare-" + substring).mergely("get","rhs");
+ //$intro.val($toMerge);
- // Confirmation message
- var msg = "";
+ //// Confirmation message
+ //var msg = "";
- button.before(msg);
+ //button.before(msg);
- setTimeout(function() {
- $(".alert-merge").fadeOut("fast");
- }, 2000);
- }
- });
- });
- });
-})(jQuery);
+ //setTimeout(function() {
+ //$(".alert-merge").fadeOut("fast");
+ //}, 2000);
+ //}
+ //});
+ //});
+ //});
+//})(jQuery);
diff --git a/assets/scss/main.scss b/assets/scss/main.scss
index 0c9fc45eab..dab5607444 100644
--- a/assets/scss/main.scss
+++ b/assets/scss/main.scss
@@ -64,7 +64,6 @@
@import "components/featured-item";
@import "components/markdown-help";
@import "components/mobile-menu";
-@import "components/mergely";
@import "components/modals";
@import "components/pagination";
@import "components/pygments";
diff --git a/package.json b/package.json
index 7e7b732288..c7d37e1947 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,6 @@
"gulp-uglify": "3.0.0",
"gulp.spritesmith": "6.5.1",
"jquery": "3.2.1",
- "mergely": "3.4.0",
"normalize.css": "7.0.0"
},
"devDependencies": {
From fcd9eee6e90185fab4d799ae9a5aeba4ef6d9802 Mon Sep 17 00:00:00 2001
From: Antonin
Date: Mon, 18 Sep 2017 08:32:09 +0200
Subject: [PATCH 70/78] Working example
---
Gulpfile.js | 4 +-
assets/js/auto-merge.js | 122 +++++++++++-------------
assets/scss/components/_codemirror.scss | 116 ++++++++++++++++++++++
package.json | 7 +-
templates/base.html | 1 +
yarn.lock | 34 ++++---
6 files changed, 204 insertions(+), 80 deletions(-)
diff --git a/Gulpfile.js b/Gulpfile.js
index 4e1ce5438b..d41988bcd2 100644
--- a/Gulpfile.js
+++ b/Gulpfile.js
@@ -49,9 +49,9 @@ gulp.task('js', () =>
require.resolve('jquery'),
require.resolve('cookies-eu-banner'),
require.resolve('codemirror'),
- //require('codemirror/addon/merge/merge'),
+ require.resolve('codemirror/addon/merge/merge'),
// Used by other scripts, must be first
- '../node_modules/codemirror/addon/merge/merge.js',
+ //'../node_modules/codemirror/addon/merge/merge.js',
'assets/js/modal.js',
'assets/js/tooltips.js',
diff --git a/assets/js/auto-merge.js b/assets/js/auto-merge.js
index d61c56c933..c7c64e0e43 100644
--- a/assets/js/auto-merge.js
+++ b/assets/js/auto-merge.js
@@ -1,71 +1,65 @@
-//(function($, undefined) {
- //"use strict";
+(function($, undefined) {
+ "use strict";
- //$(document).ready(function () {
+ $(document).ready(function () {
- ///**
- //* Sets up the merge interface (using mergely) in the $div Object. Data is generally retrieved from a form field
- //* or an aditionnal div exposing the old data, also generated in the form.
- //* @param {Object} $div - The base object used to set up the interface. Generally created in forms files.
- //* @param {Object} $left - The object from which we will pick the content to put in the left hand side (lhs) of the editor.
- //* @param {Object} $right - The object from which we will pick the content to put in the right hand side (rhs) of the editor.
- //*/
- //function mergelySetUp($div, $left, $right)
- //{
- //$div.mergely({
- //width: "auto",
- //height: 400,
- //sidebar: false,
- //cmsettings: { readOnly: false, lineNumbers: true, lineWrapping: true },
- //lhs: function(setValue) {
- //setValue($left.html());
- //},
- //rhs: function(setValue) {
- //setValue($right.html());
- //}
- //});
- //}
+ /**
+ * Sets up the merge interface (using codemirror) in the $div Object.
+ * Data is generally retrieved from a form field or an aditionnal
+ * div exposing the old data,also generated in the form.
+ * @param {Object} $div - The base object used to set up the interface. Generally created in forms files.
+ * @param {Object} $left - The object from which we will pick the content to put in the left hand side (lhs) of the editor.
+ * @param {Object} $right - The object from which we will pick the content to put in the right hand side (rhs) of the editor.
+ */
+ function mergeUISetUp(selector, $left, $right){
+ var target = document.getElementsByClassName(selector)[0]; // TODO remplacer par ID ou objet
+ if (target) {
+ target.innerHTML = ""
+ var merge = CodeMirror.MergeView(target, {
+ value: $left.html(),
+ orig: $right.html(),
+ lineNumbers: true,
+ highlightDifferences: true,
+ connect: "align",
+ collapseIdentical: true
+ });
+ return merge;
+ }
+ }
- //mergelySetUp($(".compare-introduction"),$("#your_introduction"),$("#id_introduction"));
- //mergelySetUp($(".compare-conclusion"),$("#your_conclusion"),$("#id_conclusion"));
- //mergelySetUp($(".compare-text"),$("#your_text"),$("#id_text"));
+ var mergeInterfaceList = {};
+ mergeInterfaceList.introduction = mergeUISetUp("compare-introduction",$("#your_introduction"),$("#id_introduction"));
+ mergeInterfaceList.conclusion = mergeUISetUp("compare-conclusion",$("#your_conclusion"),$("#id_conclusion"));
+ mergeInterfaceList.text = mergeUISetUp("compare-text",$("#your_text"),$("#id_text"));
- //$("#compare-editor-lhs").append("Votre Version");
- //$("#compare-editor-rhs").append("La version courante");
+ $(".CodeMirror-merge-editor").append("Votre Version");
+ $(".CodeMirror-merge-right").append("La version courante");
- ///**
- //* Merge content
- //*/
- //$(".merge-btn").on("click", function(e){
+ /**
+ * Merge content
+ */
+ $(".merge-btn").on("click", function(e){
+ e.stopPropagation();
+ e.preventDefault();
+ var button = $(this);
- //e.stopPropagation();
- //e.preventDefault();
+ Array.from(this.classList).forEach(function(element){
+ if (element.indexOf("need-to-merge-") >= 0) {
+ var substring = element.substring(14);
+ var toMerge = mergeInterfaceList[substring].editor().getValue();
+ $("#id_" + substring).text(toMerge);
- //var button = $(this);
-
- //Array.from(this.classList).forEach(function(element){
- //if (element.indexOf("need-to-merge-") >= 0) {
-
- //// Cut the string to get the ending part
- //var substring = element.substring(14);
-
- //var $intro = $("#id_" + substring);
- //var $toMerge = $(".compare-" + substring).mergely("get","rhs");
- //$intro.val($toMerge);
-
- //// Confirmation message
- //var msg = "";
-
- //button.before(msg);
-
- //setTimeout(function() {
- //$(".alert-merge").fadeOut("fast");
- //}, 2000);
- //}
- //});
- //});
- //});
-//})(jQuery);
+ // Confirmation message
+ var msg = "";
+ button.before(msg);
+ setTimeout(function() {
+ $(".alert-merge").fadeOut("fast");
+ }, 2000);
+ }
+ });
+ });
+ });
+})(jQuery);
diff --git a/assets/scss/components/_codemirror.scss b/assets/scss/components/_codemirror.scss
index 1067b3ee6b..62b88de4a2 100644
--- a/assets/scss/components/_codemirror.scss
+++ b/assets/scss/components/_codemirror.scss
@@ -332,3 +332,119 @@ div.CodeMirror-dragcursors {
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }
+
+/* MERGE ADDON */
+.CodeMirror-merge {
+ position: relative;
+ border: 1px solid #ddd;
+ white-space: pre;
+}
+
+.CodeMirror-merge, .CodeMirror-merge .CodeMirror {
+ height: 350px;
+}
+
+.CodeMirror-merge-2pane .CodeMirror-merge-pane { width: 47%; }
+.CodeMirror-merge-2pane .CodeMirror-merge-gap { width: 6%; }
+.CodeMirror-merge-3pane .CodeMirror-merge-pane { width: 31%; }
+.CodeMirror-merge-3pane .CodeMirror-merge-gap { width: 3.5%; }
+
+.CodeMirror-merge-pane {
+ display: inline-block;
+ white-space: normal;
+ vertical-align: top;
+}
+.CodeMirror-merge-pane-rightmost {
+ position: absolute;
+ right: 0px;
+ z-index: 1;
+}
+
+.CodeMirror-merge-gap {
+ z-index: 2;
+ display: inline-block;
+ height: 100%;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ overflow: hidden;
+ border-left: 1px solid #ddd;
+ border-right: 1px solid #ddd;
+ position: relative;
+ background: #f8f8f8;
+}
+
+.CodeMirror-merge-scrolllock-wrap {
+ position: absolute;
+ bottom: 0; left: 50%;
+}
+.CodeMirror-merge-scrolllock {
+ position: relative;
+ left: -50%;
+ cursor: pointer;
+ color: #555;
+ line-height: 1;
+}
+
+.CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right {
+ position: absolute;
+ left: 0; top: 0;
+ right: 0; bottom: 0;
+ line-height: 1;
+}
+
+.CodeMirror-merge-copy {
+ position: absolute;
+ cursor: pointer;
+ color: #44c;
+ z-index: 3;
+}
+
+.CodeMirror-merge-copy-reverse {
+ position: absolute;
+ cursor: pointer;
+ color: #44c;
+}
+
+.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; }
+.CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; }
+
+.CodeMirror-merge-r-inserted, .CodeMirror-merge-l-inserted {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg==);
+ background-position: bottom left;
+ background-repeat: repeat-x;
+}
+
+.CodeMirror-merge-r-deleted, .CodeMirror-merge-l-deleted {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg==);
+ background-position: bottom left;
+ background-repeat: repeat-x;
+}
+
+.CodeMirror-merge-r-chunk { background: #ffffe0; }
+.CodeMirror-merge-r-chunk-start { border-top: 1px solid #ee8; }
+.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #ee8; }
+.CodeMirror-merge-r-connect { fill: #ffffe0; stroke: #ee8; stroke-width: 1px; }
+
+.CodeMirror-merge-l-chunk { background: #eef; }
+.CodeMirror-merge-l-chunk-start { border-top: 1px solid #88e; }
+.CodeMirror-merge-l-chunk-end { border-bottom: 1px solid #88e; }
+.CodeMirror-merge-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; }
+
+.CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; }
+.CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; }
+.CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; }
+
+.CodeMirror-merge-collapsed-widget:before {
+ content: "(...)";
+}
+.CodeMirror-merge-collapsed-widget {
+ cursor: pointer;
+ color: #88b;
+ background: #eef;
+ border: 1px solid #ddf;
+ font-size: 90%;
+ padding: 0 3px;
+ border-radius: 4px;
+}
+.CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { display: none; }
+
diff --git a/package.json b/package.json
index c7d37e1947..67342ac41b 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,8 @@
"directories": {
"doc": "doc"
},
- "engines" : {
- "node" : ">=8.0.0"
+ "engines": {
+ "node": ">=8.0.0"
},
"scripts": {
"gulp": "gulp",
@@ -31,7 +31,8 @@
"cookies-eu-banner": "1.2.8",
"cssnano": "3.10.0",
"del": "3.0.0",
- "gulp": "3.9.1",
+ "diff-merge-patch": "^0.6.0",
+ "gulp": "^3.9.1",
"gulp-concat": "2.6.1",
"gulp-imagemin": "3.3.0",
"gulp-postcss": "7.0.0",
diff --git a/templates/base.html b/templates/base.html
index 96a15b4641..cde5bda1fa 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -669,6 +669,7 @@ {{ headlin
{# Javascript stuff start #}
+
{% block extra_js %}
diff --git a/yarn.lock b/yarn.lock
index 2aae4929fd..c99a7bf5d3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -535,7 +535,7 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
-codemirror@^5.10.0, codemirror@^5.28.0:
+codemirror@^5.28.0:
version "5.28.0"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.28.0.tgz#2978d9280d671351a4f5737d06bbd681a0fd6f83"
@@ -942,6 +942,21 @@ detect-newline@2.X:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
+diff-merge-patch@0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/diff-merge-patch/-/diff-merge-patch-0.4.0.tgz#c13251422e53fa4b72b2f7c3a3afd1cba2763ec8"
+ dependencies:
+ longest-common-substring "0.0.1"
+ underscore "~1.4.3"
+
+diff-merge-patch@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/diff-merge-patch/-/diff-merge-patch-0.6.0.tgz#3ff2886f00ce1de3d5f455e0bfee0b8ad950322b"
+ dependencies:
+ diff-merge-patch "0.4.0"
+ longest-common-substring "0.0.1"
+ underscore "~1.4.3"
+
dom-serializer@0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
@@ -1838,7 +1853,7 @@ gulp.spritesmith@6.5.1:
underscore "~1.8.3"
url2 "~1.0.4"
-gulp@3.9.1:
+gulp@^3.9.1:
version "3.9.1"
resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4"
dependencies:
@@ -2337,7 +2352,7 @@ jpegtran-bin@^3.0.0:
bin-wrapper "^3.0.0"
logalot "^2.0.0"
-jquery@3.2.1, jquery@>=1.1.0:
+jquery@3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787"
@@ -2663,6 +2678,10 @@ logalot@^2.0.0:
figures "^1.3.5"
squeak "^1.0.0"
+longest-common-substring@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/longest-common-substring/-/longest-common-substring-0.0.1.tgz#d91f8d08ab5d2debc9914fca8d4b91413956acc8"
+
longest@^1.0.0, longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
@@ -2778,13 +2797,6 @@ merge-stream@^1.0.0:
dependencies:
readable-stream "^2.0.1"
-mergely@3.4.4:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/mergely/-/mergely-3.4.0.tgz#c48d295ace372ac7e40743b590dfb301f206e918"
- dependencies:
- codemirror "^5.10.0"
- jquery ">=1.1.0"
-
micromatch@^2.3.7:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
@@ -4454,7 +4466,7 @@ underscore.string@~3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.0.3.tgz#4617b8c1a250cf6e5064fbbb363d0fa96cf14552"
-underscore@~1.4.2:
+underscore@~1.4.2, underscore@~1.4.3:
version "1.4.4"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604"
From 3df61ecf980d80a7254a5c87787ba97da0e972f8 Mon Sep 17 00:00:00 2001
From: Antonin
Date: Sat, 28 Oct 2017 15:49:06 +0200
Subject: [PATCH 71/78] Code style
---
Gulpfile.js | 1 -
assets/scss/components/_auto-merge.scss | 4 ++--
templates/base.html | 5 +----
templates/tutorialv2/edit/container.html | 6 +++++-
templates/tutorialv2/edit/content.html | 6 +++++-
templates/tutorialv2/edit/extract.html | 5 ++++-
zds/settings.py | 1 -
zds/tutorialv2/forms.py | 12 ++++++------
8 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/Gulpfile.js b/Gulpfile.js
index d41988bcd2..5c56fac08c 100644
--- a/Gulpfile.js
+++ b/Gulpfile.js
@@ -51,7 +51,6 @@ gulp.task('js', () =>
require.resolve('codemirror'),
require.resolve('codemirror/addon/merge/merge'),
// Used by other scripts, must be first
- //'../node_modules/codemirror/addon/merge/merge.js',
'assets/js/modal.js',
'assets/js/tooltips.js',
diff --git a/assets/scss/components/_auto-merge.scss b/assets/scss/components/_auto-merge.scss
index 9e1da08346..b2a9f53e5e 100644
--- a/assets/scss/components/_auto-merge.scss
+++ b/assets/scss/components/_auto-merge.scss
@@ -1,3 +1,3 @@
#compare-lhs-margin, #compare-rhs-margin {
- display : none;
-}
\ No newline at end of file
+ display: none;
+}
diff --git a/templates/base.html b/templates/base.html
index cde5bda1fa..9a51017562 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -669,12 +669,9 @@ {{ headlin
{# Javascript stuff start #}
-
-
-
{% block extra_js %}
{% endblock %}
-
+
{# Google Analytics #}
diff --git a/templates/tutorialv2/edit/container.html b/templates/tutorialv2/edit/container.html
index 4810559c08..af222c4802 100644
--- a/templates/tutorialv2/edit/container.html
+++ b/templates/tutorialv2/edit/container.html
@@ -3,6 +3,10 @@
{% load i18n %}
{% load feminize %}
+{% block extra_js %}
+
+{% endblock %}
+
{% block title %}
{% trans "Éditer " %}{{ "un"|feminize:container.get_level_as_string }} {{ container.get_level_as_string|lower }}
{% endblock %}
@@ -44,4 +48,4 @@
{% trans "Ajouter une image à la galerie" %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/tutorialv2/edit/content.html b/templates/tutorialv2/edit/content.html
index 74f55f0ba0..9bbb0d2b51 100644
--- a/templates/tutorialv2/edit/content.html
+++ b/templates/tutorialv2/edit/content.html
@@ -4,6 +4,10 @@
{% load i18n %}
{% load feminize %}
+{% block extra_js %}
+
+{% endblock %}
+
{% block title %}
{% trans "Éditer " %}{{ content.textual_type }}
{% endblock %}
@@ -42,4 +46,4 @@
{% trans "Ajouter une image à la galerie" %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/templates/tutorialv2/edit/extract.html b/templates/tutorialv2/edit/extract.html
index 7979ecfbd1..ad28f2fa8b 100644
--- a/templates/tutorialv2/edit/extract.html
+++ b/templates/tutorialv2/edit/extract.html
@@ -2,6 +2,9 @@
{% load crispy_forms_tags %}
{% load i18n %}
+{% block extra_js %}
+
+{% endblock %}
{% block title %}
{% trans "Éditer la section" %}
@@ -50,4 +53,4 @@
{% trans "Ajouter une image à la galerie" %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/zds/settings.py b/zds/settings.py
index 371e3624d1..4f8ebfd635 100644
--- a/zds/settings.py
+++ b/zds/settings.py
@@ -17,7 +17,6 @@
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DEBUG = True
-
INTERNAL_IPS = ('127.0.0.1',) # debug toolbar
DATABASES = {
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index e142253043..00743b4fb2 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -179,9 +179,9 @@ def __init__(self, *args, **kwargs):
old_intro = ''
self.helper.layout.append(Layout(Field('introduction', css_class='hidden')))
- self.helper.layout.append(Layout(HTML('
' +
- old_intro + '
')))
- self.helper.layout.append(Layout(HTML('')))
+ self.helper.layout.append(Layout(HTML('{}
'
+ .format(old_intro))))
+ self.helper.layout.append(Layout(HTML('')))
self.helper.layout.append(Layout(
ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
@@ -192,9 +192,9 @@ def __init__(self, *args, **kwargs):
old_conclusion = ''
self.helper.layout.append(Layout(Field('conclusion', css_class='hidden')))
- self.helper.layout.append(Layout(HTML('' + old_conclusion +
- '
')))
- self.helper.layout.append(Layout(HTML('')))
+ self.helper.layout.append(Layout(HTML('{}
'
+ .format(old_conclusion))))
+ self.helper.layout.append(Layout(HTML('')))
self.helper.layout.append(Layout(
ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
From 21ee83f93103f2fa68a14c6ebf46ea3fa7ba18f7 Mon Sep 17 00:00:00 2001
From: Antonin
Date: Thu, 2 Nov 2017 09:07:02 +0100
Subject: [PATCH 72/78] Fixing merge
---
zds/tutorialv2/forms.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index f13d325ab6..4e07cfc84e 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -313,10 +313,12 @@ def _create_layout(self, hide_help, **kwargs):
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),
HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
- with text=form.conclusion.value %}{% endif %}'),
- Field('last_hash'),
- Field('licence'),
- Field('subcategory', template='crispy/checkboxselectmultiple.html'))))
+ with text=form.conclusion.value %}{% endif %}'))))
+
+ self.helper.layout.append(Layout(
+ Field('last_hash'),
+ Field('licence'),
+ Field('subcategory', template='crispy/checkboxselectmultiple.html')))
if not hide_help:
self.helper.layout.append(html_part)
From 1623c635754247234448412f8f60a55ecc088195 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Thu, 2 Nov 2017 09:36:47 +0100
Subject: [PATCH 73/78] Update settings.py
---
zds/settings.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/zds/settings.py b/zds/settings.py
index 3607e3cfd5..b1c9b55cdd 100644
--- a/zds/settings.py
+++ b/zds/settings.py
@@ -14,6 +14,7 @@
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DEBUG = True
+
INTERNAL_IPS = ('127.0.0.1',) # debug toolbar
DATABASES = {
From dd97dde67ac0473571ab72f51d4da7cf40eabebe Mon Sep 17 00:00:00 2001
From: Antonin
Date: Sun, 5 Nov 2017 11:29:44 +0100
Subject: [PATCH 74/78] Refactoring
---
assets/js/auto-merge.js | 74 ++++++++++++++++++++---------------------
zds/tutorialv2/forms.py | 54 +++---------------------------
zds/utils/forms.py | 31 +++++++++++------
3 files changed, 61 insertions(+), 98 deletions(-)
diff --git a/assets/js/auto-merge.js b/assets/js/auto-merge.js
index c7c64e0e43..ee4659b922 100644
--- a/assets/js/auto-merge.js
+++ b/assets/js/auto-merge.js
@@ -1,20 +1,20 @@
(function($, undefined) {
"use strict";
- $(document).ready(function () {
+ $(document).ready(function () {
- /**
- * Sets up the merge interface (using codemirror) in the $div Object.
+ /**
+ * Sets up the merge interface (using codemirror) in the $div Object.
* Data is generally retrieved from a form field or an aditionnal
* div exposing the old data,also generated in the form.
- * @param {Object} $div - The base object used to set up the interface. Generally created in forms files.
+ * @param {Object} $div - The base object used to set up the interface. Generally created in forms files.
* @param {Object} $left - The object from which we will pick the content to put in the left hand side (lhs) of the editor.
* @param {Object} $right - The object from which we will pick the content to put in the right hand side (rhs) of the editor.
- */
- function mergeUISetUp(selector, $left, $right){
+ */
+ function mergeUISetUp(selector, $left, $right){
var target = document.getElementsByClassName(selector)[0]; // TODO remplacer par ID ou objet
if (target) {
- target.innerHTML = ""
+ target.innerHTML = "";
var merge = CodeMirror.MergeView(target, {
value: $left.html(),
orig: $right.html(),
@@ -25,41 +25,41 @@
});
return merge;
}
- }
+ }
var mergeInterfaceList = {};
- mergeInterfaceList.introduction = mergeUISetUp("compare-introduction",$("#your_introduction"),$("#id_introduction"));
- mergeInterfaceList.conclusion = mergeUISetUp("compare-conclusion",$("#your_conclusion"),$("#id_conclusion"));
- mergeInterfaceList.text = mergeUISetUp("compare-text",$("#your_text"),$("#id_text"));
+ mergeInterfaceList.introduction = mergeUISetUp("compare-introduction",$("#your_introduction"),$("#id_introduction"));
+ mergeInterfaceList.conclusion = mergeUISetUp("compare-conclusion",$("#your_conclusion"),$("#id_conclusion"));
+ mergeInterfaceList.text = mergeUISetUp("compare-text",$("#your_text"),$("#id_text"));
- $(".CodeMirror-merge-editor").append("Votre Version");
+ $(".CodeMirror-merge-editor").append("Votre Version");
$(".CodeMirror-merge-right").append("La version courante");
- /**
- * Merge content
- */
- $(".merge-btn").on("click", function(e){
- e.stopPropagation();
- e.preventDefault();
- var button = $(this);
+ /**
+ * Merge content
+ */
+ $(".merge-btn").on("click", function(e){
+ e.stopPropagation();
+ e.preventDefault();
+ var button = $(this);
- Array.from(this.classList).forEach(function(element){
- if (element.indexOf("need-to-merge-") >= 0) {
- var substring = element.substring(14);
- var toMerge = mergeInterfaceList[substring].editor().getValue();
- $("#id_" + substring).text(toMerge);
+ Array.from(this.classList).forEach(function(element){
+ if (element.indexOf("need-to-merge-") >= 0) {
+ var substring = element.substring(14);
+ var toMerge = mergeInterfaceList[substring].editor().getValue();
+ $("#id_" + substring).text(toMerge);
- // Confirmation message
- var msg = "";
- button.before(msg);
- setTimeout(function() {
- $(".alert-merge").fadeOut("fast");
- }, 2000);
- }
- });
- });
- });
+ // Confirmation message
+ var msg = "";
+ button.before(msg);
+ setTimeout(function() {
+ $(".alert-merge").fadeOut("fast");
+ }, 2000);
+ }
+ });
+ });
+ });
})(jQuery);
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 4e07cfc84e..2750555b05 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -1,5 +1,3 @@
-# coding: utf-8
-
from django import forms
from django.conf import settings
@@ -16,7 +14,7 @@
from django.utils.translation import ugettext_lazy as _
from zds.member.models import Profile
from zds.tutorialv2.utils import slugify_raise_on_invalid, InvalidSlugError
-from zds.utils.forms import TagValidator
+from zds.utils.forms import TagValidator, MergeableFieldMixin
class FormWithTitle(forms.Form):
@@ -50,26 +48,6 @@ def clean(self):
return cleaned_data
-class MergeableFieldMixin():
- # TODO: utiliser ce mixin à d'autres endroits ?
- def add_merge_interface_to_field(self, field_name, **kwargs):
-
- field_old_content = kwargs.get('data').get(field_name)
- if field_old_content is None:
- field_old_content = ''
- self.helper.layout.append(Layout(Field(field_name, css_class='hidden')))
-
- self.helper.layout.append(
- Layout(HTML('{1}
'
- .format(field_name, field_old_content))))
- self.helper.layout.append(
- Layout(HTML(''
- .format(field_name))))
- self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
- css_class='btn btn-submit merge-btn need-to-merge-{0}'.format(field_name)))))
-
-
class AuthorForm(forms.Form):
username = forms.CharField(
@@ -129,7 +107,7 @@ def clean_username(self):
return cleaned_data
-class ContainerForm(FormWithTitle):
+class ContainerForm(FormWithTitle, MergeableFieldMixin):
introduction = forms.CharField(
label=_('Introduction'),
@@ -174,33 +152,9 @@ def __init__(self, *args, **kwargs):
self.helper.layout = Layout(Field('title'))
if kwargs.get('data', None) is not None:
- old_intro = kwargs.get('data').get('introduction')
- if old_intro is None:
- old_intro = ''
-
- self.helper.layout.append(Layout(Field('introduction', css_class='hidden')))
- self.helper.layout.append(Layout(HTML('{}
'
- .format(old_intro))))
- self.helper.layout.append(Layout(HTML('')))
-
- self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
- css_class='btn btn-submit merge-btn need-to-merge-introduction'))))
-
- old_conclusion = kwargs.get('data').get('conclusion')
- if old_conclusion is None:
- old_conclusion = ''
-
- self.helper.layout.append(Layout(Field('conclusion', css_class='hidden')))
- self.helper.layout.append(Layout(HTML('{}
'
- .format(old_conclusion))))
- self.helper.layout.append(Layout(HTML('')))
-
- self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
- css_class='btn btn-submit merge-btn need-to-merge-conclusion'))))
+ self.add_merge_interface_to_field('introduction', **kwargs)
+ self.add_merge_interface_to_field('conclusion', **kwargs)
else:
-
self.helper.layout.append(Layout(
Field('introduction', css_class='md-editor preview-source'),
ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
diff --git a/zds/utils/forms.py b/zds/utils/forms.py
index 3bde2f09f5..74f9f6aef3 100644
--- a/zds/utils/forms.py
+++ b/zds/utils/forms.py
@@ -1,4 +1,3 @@
-# coding: utf-8
import logging
from crispy_forms.bootstrap import StrictButton
@@ -9,6 +8,24 @@
from zds.utils.misc import contains_utf8mb4
+class MergeableFieldMixin():
+ def add_merge_interface_to_field(self, field_name, **kwargs):
+ field_old_content = kwargs.get('data').get(field_name)
+ if field_old_content is None:
+ field_old_content = ''
+ self.helper.layout.append(Layout(Field(field_name, css_class='hidden')))
+
+ self.helper.layout.append(
+ Layout(HTML('{1}
'
+ .format(field_name, field_old_content))))
+ self.helper.layout.append(
+ Layout(HTML(''
+ .format(field_name))))
+ self.helper.layout.append(Layout(
+ ButtonHolder(StrictButton(_('Valider cette version'), type='merge', name='merge',
+ css_class='btn btn-submit merge-btn need-to-merge-{0}'.format(field_name)))))
+
+
class CommonLayoutEditor(Layout):
def __init__(self, *args, **kwargs):
@@ -32,20 +49,12 @@ def __init__(self, *args, **kwargs):
)
-class CommonLayoutVersionEditor(Layout):
+class CommonLayoutVersionEditor(Layout, MergeableFieldMixin):
def __init__(self, *args, **kwargs):
if kwargs.get('data', None) is not None:
- old_text = kwargs.get('data').get('text')
-
- text_field = Field('text', css_class='hidden')
- text_field += HTML('' + old_text + '
')
- text_field += HTML('')
-
- text_field += ButtonHolder(StrictButton(_(u'Valider cette version'), type='merge', name='merge',
- css_class='btn btn-submit merge-btn need-to-merge-text'))
-
+ add_merge_interface_to_field('text', **kwargs)
else:
text_field = Field('text', css_class='md-editor')
From 909b53e432cc667ea698ad9a8edd4f439ec0476b Mon Sep 17 00:00:00 2001
From: Antonin
Date: Sun, 5 Nov 2017 15:33:40 +0100
Subject: [PATCH 75/78] Linting
---
zds/tutorialv2/forms.py | 25 ++++++++++++-------------
zds/utils/forms.py | 32 ++++++++++++++++----------------
2 files changed, 28 insertions(+), 29 deletions(-)
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 4b3af5251f..0969f92753 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -157,15 +157,15 @@ def __init__(self, *args, **kwargs):
else:
self.helper.layout.append(Layout(
Field('introduction', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),),
HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
with text=form.introduction.value %}{% endif %}'),
Field('conclusion', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),
- HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
- with text=form.conclusion.value %}{% endif %}'))))
+ HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
+ with text=form.conclusion.value %}{% endif %}'))))
self.helper.layout.append(Layout(
Field('msg_commit'),
@@ -176,7 +176,7 @@ def __init__(self, *args, **kwargs):
type='submit'),
)
))
-
+
class ContentForm(ContainerForm, MergeableFieldMixin):
@@ -237,7 +237,6 @@ class ContentForm(ContainerForm, MergeableFieldMixin):
widget=forms.CheckboxSelectMultiple()
)
-
def _create_layout(self, hide_help, **kwargs):
html_part = HTML(_("Demander de l'aide à la communauté !
"
"Si vous avez besoin d'un coup de main, "
@@ -259,20 +258,20 @@ def _create_layout(self, hide_help, **kwargs):
else:
self.helper.layout.append(Layout(
Field('introduction', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),),
HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \
with text=form.introduction.value %}{% endif %}'),
Field('conclusion', css_class='md-editor preview-source'),
- ButtonHolder(StrictButton(_(u'Aperçu'), type='preview', name='preview',
+ ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview',
css_class='btn btn-grey preview-btn'),
- HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
- with text=form.conclusion.value %}{% endif %}'))))
+ HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \
+ with text=form.conclusion.value %}{% endif %}'))))
self.helper.layout.append(Layout(
- Field('last_hash'),
- Field('licence'),
- Field('subcategory', template='crispy/checkboxselectmultiple.html')))
+ Field('last_hash'),
+ Field('licence'),
+ Field('subcategory', template='crispy/checkboxselectmultiple.html')))
if not hide_help:
self.helper.layout.append(html_part)
diff --git a/zds/utils/forms.py b/zds/utils/forms.py
index 74f9f6aef3..f831320dce 100644
--- a/zds/utils/forms.py
+++ b/zds/utils/forms.py
@@ -9,21 +9,21 @@
class MergeableFieldMixin():
- def add_merge_interface_to_field(self, field_name, **kwargs):
- field_old_content = kwargs.get('data').get(field_name)
- if field_old_content is None:
- field_old_content = ''
- self.helper.layout.append(Layout(Field(field_name, css_class='hidden')))
-
- self.helper.layout.append(
- Layout(HTML('
{1}
'
- .format(field_name, field_old_content))))
- self.helper.layout.append(
- Layout(HTML(''
- .format(field_name))))
- self.helper.layout.append(Layout(
- ButtonHolder(StrictButton(_('Valider cette version'), type='merge', name='merge',
- css_class='btn btn-submit merge-btn need-to-merge-{0}'.format(field_name)))))
+ def add_merge_interface_to_field(self, field_name, **kwargs):
+ field_old_content = kwargs.get('data').get(field_name)
+ if field_old_content is None:
+ field_old_content = ''
+ self.helper.layout.append(Layout(Field(field_name, css_class='hidden')))
+
+ self.helper.layout.append(
+ Layout(HTML('{1}
'
+ .format(field_name, field_old_content))))
+ self.helper.layout.append(
+ Layout(HTML(''
+ .format(field_name))))
+ self.helper.layout.append(Layout(
+ ButtonHolder(StrictButton(_('Valider cette version'), type='merge', name='merge',
+ css_class='btn btn-submit merge-btn need-to-merge-{0}'.format(field_name)))))
class CommonLayoutEditor(Layout):
@@ -54,7 +54,7 @@ class CommonLayoutVersionEditor(Layout, MergeableFieldMixin):
def __init__(self, *args, **kwargs):
if kwargs.get('data', None) is not None:
- add_merge_interface_to_field('text', **kwargs)
+ self.add_merge_interface_to_field('text', **kwargs)
else:
text_field = Field('text', css_class='md-editor')
From af170c0dc1ceee6786fda25057fde964aecdedcc Mon Sep 17 00:00:00 2001
From: Antonin
Date: Sun, 5 Nov 2017 16:02:20 +0100
Subject: [PATCH 76/78] Linting and remove py2 u
---
assets/js/auto-merge.js | 2 +-
zds/tutorialv2/forms.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/assets/js/auto-merge.js b/assets/js/auto-merge.js
index ee4659b922..605a908e6a 100644
--- a/assets/js/auto-merge.js
+++ b/assets/js/auto-merge.js
@@ -46,7 +46,7 @@
Array.from(this.classList).forEach(function(element){
if (element.indexOf("need-to-merge-") >= 0) {
var substring = element.substring(14);
- var toMerge = mergeInterfaceList[substring].editor().getValue();
+ var toMerge = mergeInterfaceList[substring].editor().getValue();
$("#id_" + substring).text(toMerge);
// Confirmation message
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 0969f92753..f6e6627f55 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -172,7 +172,7 @@ def __init__(self, *args, **kwargs):
Field('last_hash'),
ButtonHolder(
StrictButton(
- _(u'Valider'),
+ _('Valider'),
type='submit'),
)
))
From b95cc1701d589a6261dbed16f5765d508ac894a7 Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Sun, 5 Nov 2017 20:16:42 +0100
Subject: [PATCH 77/78] Update auto-merge.js
---
assets/js/auto-merge.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/auto-merge.js b/assets/js/auto-merge.js
index 605a908e6a..f2a6d83038 100644
--- a/assets/js/auto-merge.js
+++ b/assets/js/auto-merge.js
@@ -15,7 +15,7 @@
var target = document.getElementsByClassName(selector)[0]; // TODO remplacer par ID ou objet
if (target) {
target.innerHTML = "";
- var merge = CodeMirror.MergeView(target, {
+ var merge = window.CodeMirror.MergeView(target, {
value: $left.html(),
orig: $right.html(),
lineNumbers: true,
From 88a9290e1e4c5b72553b9d583244758725b7cade Mon Sep 17 00:00:00 2001
From: Anto59290
Date: Mon, 6 Nov 2017 08:24:04 +0100
Subject: [PATCH 78/78] Update auto-merge.js
---
assets/js/auto-merge.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/auto-merge.js b/assets/js/auto-merge.js
index f2a6d83038..cbfa427152 100644
--- a/assets/js/auto-merge.js
+++ b/assets/js/auto-merge.js
@@ -1,4 +1,4 @@
-(function($, undefined) {
+(function ($, undefined) {
"use strict";
$(document).ready(function () {