diff --git a/.travis.yml b/.travis.yml index db94f8e6bd..40342afae8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,7 +55,7 @@ install: - sudo apt-get --reinstall install -qq language-pack-fr # Python dependencies - - travis_retry pip install -r requirements.txt + - travis_retry pip install -r requirements.txt -r requirements-dev.txt - travis_retry pip install coveralls - travis_retry pip install MySQL-python diff --git a/AUTHORS b/AUTHORS index 5b8137489d..f3202c84ac 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,8 +16,10 @@ Original code base (pdp / fork on 11-02-2013) : Romain Porte (MicroJoe)

Prévisualisation de votre message

"+data.text+"
"); + $prev.insertAfter($form); + } + }); + e.stopPropagation(); + e.preventDefault(); + }); +})(jQuery); diff --git a/assets/js/autocompletion.js b/assets/js/autocompletion.js index 125ad1f8de..7b23acff61 100644 --- a/assets/js/autocompletion.js +++ b/assets/js/autocompletion.js @@ -193,6 +193,12 @@ return false; }, + filterData: function(data, exclude){ + return data.filter(function(e){ + return exclude.indexOf(e.value) === -1; + }); + }, + updateDropdown: function(list){ var self = this; var onClick = function(e){ @@ -203,6 +209,8 @@ self.handleInput(); }; + list = self.filterData(list, self.extractWords(this.$input.val())); + if(list.length > this.options.limit) list = list.slice(0, this.options.limit); var $list = $(" + + {% include "misc/social_buttons.part.html" with link=article.get_absolute_url_online text=article.title %} {% endblock %} diff --git a/templates/base.html b/templates/base.html index 07417c9d16..d92e85930f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -46,7 +46,7 @@ {% captureas meta_image %} - {{ request.META.HTTP_HOST }}{% block meta_image %}{% spaceless %} + {{ app.site.dns }}{% block meta_image %}{% spaceless %} {% static "images/apple-touch-icon-144x144-precomposed.png" %} {% endspaceless %}{% endblock %} {% endcaptureas %} @@ -77,9 +77,9 @@ {# Stylesheets #} {% if debug %} - + {% else %} - + {% block canonical %}{% endblock %} {% endif %} @@ -186,10 +186,10 @@ - {% for subcat in subcats %} + {% for subcat,slug in subcats %}
  • - - {{ subcat.title }} + + {{ subcat }}
  • {% endfor %} @@ -473,7 +473,7 @@ - + + @@ -563,10 +563,10 @@

    {{ headlin {# Javascript stuff start #} {% if debug %} - - + + {% else %} - + {% endif %} diff --git a/templates/email/mp/new.txt b/templates/email/mp/new.txt index 0c1b754c57..f0ca60473f 100644 --- a/templates/email/mp/new.txt +++ b/templates/email/mp/new.txt @@ -5,6 +5,6 @@ {% trans "Pour le lire, cliquez ou recopiez l'url suivante" %} : {{ url }} -{% trans "Cordialement, +{% trans "Cordialement" %}, -L'équipe" %} {{app.site.litteral_name}} \ No newline at end of file +{% trans "L'équipe" %} {{app.site.litteral_name}} diff --git a/templates/forum/topic/index.html b/templates/forum/topic/index.html index 4d6a9a340f..6cda5058ec 100644 --- a/templates/forum/topic/index.html +++ b/templates/forum/topic/index.html @@ -63,11 +63,10 @@ {% include "misc/pagination.part.html" with position="top" %} - {% if topic.is_solved %} -
    +
    {% trans "L'auteur de ce sujet a trouvé une solution à son problème" %}.
    - {% endif %} + {% for message in posts %} {% captureas edit_link %} @@ -94,6 +93,10 @@ {% url "zds.forum.views.solve_alert" %} {% endcaptureas %} + {% captureas unread_link %} + {% url "zds.forum.views.unread_post" %}?message={{ message.pk }} + {% endcaptureas %} + {% if forloop.first and nb > 1 %} {% set True as is_repeated_message %} {% else %} @@ -106,7 +109,7 @@ {% set True as answer_schema %} {% endif %} - {% include "misc/message.part.html" with perms_change=perms.forum.change_topic answer_schema=answer_schema %} + {% include "misc/message.part.html" with perms_change=perms.forum.change_topic answer_schema=answer_schema can_unread=True unread_link=unread_link %} {% endfor %} {% include "misc/pagination.part.html" with position="bottom" %} @@ -140,7 +143,7 @@ {% csrf_token %} -
    diff --git a/templates/misc/social_buttons.part.html b/templates/misc/social_buttons.part.html new file mode 100644 index 0000000000..97ff25f913 --- /dev/null +++ b/templates/misc/social_buttons.part.html @@ -0,0 +1,35 @@ +{% load i18n %} + + \ No newline at end of file diff --git a/templates/pages/about.html b/templates/pages/about.html index 12780fbd2c..fbe0f13f83 100644 --- a/templates/pages/about.html +++ b/templates/pages/about.html @@ -47,7 +47,7 @@

    {% trans "Droits" %}

    {{ logo_desc }} {% endblocktrans %} - Logo de {{app.site.litteral_name}} + Logo de {{app.site.litteral_name}}

    {% endif %} diff --git a/templates/tutorial/chapter/view_online.html b/templates/tutorial/chapter/view_online.html index f7bba23647..03fd9ec1e7 100644 --- a/templates/tutorial/chapter/view_online.html +++ b/templates/tutorial/chapter/view_online.html @@ -58,7 +58,7 @@

    {% if tutorial.licence %} - {% trans "Licence" %} {{ tutorial.licence }} + {{ tutorial.licence }} {% endif %} @@ -90,4 +90,6 @@

    {% blocktrans %}Administration{% endblocktrans %}< {% endif %} {% include "tutorial/includes/summary.part.html" with online=True tutorial=chapter.part.tutorial chapter_current=chapter %} + + {% include "misc/social_buttons.part.html" with link=chapter.part.tutorial.get_absolute_url_online text=chapter.part.tutorial.title %} {% endblock %} \ No newline at end of file diff --git a/templates/tutorial/comment/new.html b/templates/tutorial/comment/new.html index be7c919ff9..ad89850bc5 100644 --- a/templates/tutorial/comment/new.html +++ b/templates/tutorial/comment/new.html @@ -52,7 +52,7 @@

    {{ tutorial.description }}

    {% endcaptureas %} {% captureas cite_link %} - {% url "zds.tutorial.views.answer" %}?tutorial={{ topic.pk }}&cite={{ message.pk }} + {% url "zds.tutorial.views.answer" %}?tutorial={{ tutorial.pk }}&cite={{ message.pk }} {% endcaptureas %} {% captureas upvote_link %} @@ -71,4 +71,4 @@

    {{ tutorial.description }}

    {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/tutorial/includes/chapter.part.html b/templates/tutorial/includes/chapter.part.html index e4db36f9f5..9e04c25623 100644 --- a/templates/tutorial/includes/chapter.part.html +++ b/templates/tutorial/includes/chapter.part.html @@ -5,15 +5,15 @@ {% with extracts=chapter.extracts %} {% if not chapter.type = 'MINI' %} - {% if chapter.intro and chapter.intro != "None" %} - {{ chapter.intro|emarkdown }} + {% if chapter.intro and chapter.intro != None %} + {{ chapter.intro|emarkdown:is_js }} {% elif not tutorial.is_beta %}

    {% trans "Il n'y a pas d'introduction" %}.

    {% endif %} {% endif %} - +
    {% if not extracts %} @@ -93,7 +93,7 @@

    {% endif %} {% if extract.txt %} - {{ extract.txt|emarkdown }} + {{ extract.txt|emarkdown:is_js }} {% else %}

    {% trans "Cet extrait est vide" %}. @@ -104,8 +104,8 @@


    {% if not chapter.type = 'MINI' %} - {% if chapter.conclu and chapter.conclu != "None" %} - {{ chapter.conclu|emarkdown }} + {% if chapter.conclu and chapter.conclu != None %} + {{ chapter.conclu|emarkdown:is_js }} {% elif not tutorial.is_beta %}

    {% trans "Il n'y a pas de conclusion" %}. diff --git a/templates/tutorial/index.html b/templates/tutorial/index.html index e322b77ebf..5941470c66 100644 --- a/templates/tutorial/index.html +++ b/templates/tutorial/index.html @@ -17,7 +17,7 @@ {% if tag %} {% trans "Découvrez tous nos tutoriels sur" %} {{ tag }}. {% trans "Vous pourrez également découvrir divers sujets tous plus intéressants les uns que les autres" %}. {% else %} - {% trans "Les tutoriels vous permettent d'apprendre divers sujets tous plus intéressants + {% trans "Les tutoriels vous permettent d'apprendre divers sujets tous plus intéressants les uns que les autres" %}. {% endif %} {% endblock %} @@ -45,7 +45,7 @@

    {% endif %} {% endblock %}

    - + {% if tutorials %} @@ -72,6 +72,10 @@

    {% trans "Nouveau tutoriel" %} + + {% trans "Aider les auteurs" %} + +

    {% blocktrans %} Catégories de tutoriels {% endblocktrans %}

    {% with categories='.'|top_categories_tuto %} diff --git a/templates/tutorial/part/view.html b/templates/tutorial/part/view.html index b33d027905..b74cb303f9 100644 --- a/templates/tutorial/part/view.html +++ b/templates/tutorial/part/view.html @@ -44,7 +44,7 @@

    {% block content %} {% if part.intro and part.intro != "None" %} - {{ part.intro|emarkdown }} + {{ part.intro|emarkdown:is_js }} {% elif not tutorial.is_beta %}

    {% trans "Il n'y a pas d'introduction" %}. @@ -91,7 +91,7 @@


    {% if part.conclu and part.conclu != "None" %} - {{ part.conclu|emarkdown }} + {{ part.conclu|emarkdown:is_js }} {% elif not tutorial.is_beta %}

    {% trans "Il n'y a pas de conclusion" %}. diff --git a/templates/tutorial/part/view_online.html b/templates/tutorial/part/view_online.html index d999d65d79..6ccc3fd876 100644 --- a/templates/tutorial/part/view_online.html +++ b/templates/tutorial/part/view_online.html @@ -45,7 +45,7 @@

    {% if tutorial.licence %} - {% trans "Licence" %} {{ tutorial.licence }} + {{ tutorial.licence }} {% endif %} @@ -75,4 +75,6 @@

    {% blocktrans %}Administration{% endblocktrans %}< {% endif %} {% include "tutorial/includes/summary.part.html" with online=True tutorial=part.tutorial parts=part.tutorial.get_parts %} + + {% include "misc/social_buttons.part.html" with link=part.tutorial.get_absolute_url_online text=part.tutorial.title %} {% endblock %} \ No newline at end of file diff --git a/templates/tutorial/tutorial/help.html b/templates/tutorial/tutorial/help.html new file mode 100644 index 0000000000..2be1609814 --- /dev/null +++ b/templates/tutorial/tutorial/help.html @@ -0,0 +1,139 @@ +{% extends "tutorial/base_online.html" %} +{% load captureas %} +{% load staticfiles %} +{% load thumbnail %} +{% load i18n %} +{% load profile %} + + + +{% block title %} + {% trans "Aider les auteurs" %} +{% endblock %} + + + +{% block breadcrumb %} +
  • {% trans "Aider les auteurs" %}
  • +{% endblock %} + + +{% block headline %} +

    {% trans "Aider les auteurs de tutoriels" %} ({{ tutorials|length }})

    +{% endblock %} + + +{% block content %} + {% include "misc/pagination.part.html" with position="top" %} + + {% if tutorials %} +
    + {% for tutorial in tutorials %} +
    + {% if tutorial.image.physical.tutorial_illu.url %} + + {% endif %} +
    +

    {{ tutorial.title }}

    + + + {% trans "Par " %} + {% for author in tutorial.authors.all %} + {% if not forloop.first %} + {% if forloop.last %} + {% trans "et" %} + {% else %} + , + {% endif %} + {% endif %} + {% if author == user %} + {% trans "vous" %} + {% else %} + + {{author.username}} + + {% endif %} + {% endfor %} + {% if not user in tutorial.authors.all %} + - {% trans "Contacter par MP" %} + {% endif %} +
    + + +
    + +
    + {% if tutorial.on_line %} + + {% blocktrans %}Tutoriel en ligne{% endblocktrans %} + + {% else %} + {% blocktrans %}Tutoriel hors-ligne{% endblocktrans %} + {% endif %} + {% if tutorial.in_beta %} + + {% blocktrans %}Bêta Active{% endblocktrans %} + + {% else %} + {% blocktrans %}Bêta Inactive{% endblocktrans %} + {% endif %} + {% for help in helps %} + + {% if help in tutorial.helps.all %} + {{help.title}} + {% else %} + {{help.title}} + {% endif %} + + {% endfor %} +
    +
    + {% endfor %} +
    + {% else %} +

    + {% trans "Aucun auteur n'a besoin d'aide pour le moment." %} +

    + {% endif %} + + {% include "misc/pagination.part.html" with position="bottom" %} +{% endblock %} + + + +{% block sidebar_new %}{% endblock %} +{% block sidebar_blocks %} + +{% endblock %} \ No newline at end of file diff --git a/templates/tutorial/tutorial/history.html b/templates/tutorial/tutorial/history.html index c5792664ef..2801282fa6 100644 --- a/templates/tutorial/tutorial/history.html +++ b/templates/tutorial/tutorial/history.html @@ -28,7 +28,7 @@

    {% if tutorial.licence %} - {% trans "Licence" %} {{ tutorial.licence }} + {{ tutorial.licence }} {% endif %} @@ -102,7 +102,7 @@

    {% blocktrans with date_version=log.time.0|humane_time tutorial_title=tutorial.title %} - Êtes-vous certain de vouloir activer la bêta sur le tutoriel + Êtes-vous certain de vouloir activer la bêta sur le tutoriel "{{ tutorial_title }}" dans sa version du {{ date_version }} ? {% endblocktrans %}

    @@ -121,7 +121,7 @@

    {% blocktrans with date_version=log.time.0|humane_time tutorial_title=tutorial.title %} - Êtes-vous certain de vouloir mettre à jour la bêta du tutoriel + Êtes-vous certain de vouloir mettre à jour la bêta du tutoriel "{{ tutorial_title }}" dans sa version du {{ date_version }} ? {% endblocktrans %}

    diff --git a/templates/tutorial/tutorial/view.html b/templates/tutorial/tutorial/view.html index 94668c83c7..c80a42361e 100644 --- a/templates/tutorial/tutorial/view.html +++ b/templates/tutorial/tutorial/view.html @@ -102,7 +102,7 @@

    {% block content %} {% if tutorial.get_introduction and tutorial.get_introduction != "None" %} - {{ tutorial.get_introduction|emarkdown }} + {{ tutorial.get_introduction|emarkdown:is_js }} {% elif not tutorial.is_beta %}

    {% trans "Il n'y a pas d'introduction" %}. @@ -136,7 +136,7 @@

    {% endif %} {% if tutorial.get_conclusion and tutorial.get_conclusion != "None" %} - {{ tutorial.get_conclusion|emarkdown }} + {{ tutorial.get_conclusion|emarkdown:is_js }} {% elif not tutorial.is_beta %}

    {% trans "Il n'y a pas de conclusion" %}. @@ -362,6 +362,20 @@

    {% trans "Validation" %}

    {% endif %} +
  • + {% if is_js %} + + {% trans "Désactiver JSFiddle" %} + + {% else %} + + {% trans "Activer JSFiddle" %} + + {% endif %} +
  • + {% if tutorial.on_line %}
  • diff --git a/templates/tutorial/tutorial/view_online.html b/templates/tutorial/tutorial/view_online.html index 6182658dcd..d63ee31784 100644 --- a/templates/tutorial/tutorial/view_online.html +++ b/templates/tutorial/tutorial/view_online.html @@ -159,7 +159,7 @@

    {% url 'zds.tutorial.views.answer' %}?tutorial={{ tutorial.pk }} {% endcaptureas %} - {% include "misc/message_form.html" with member=user %} + {% include "misc/message_form.html" with member=user topic=tutorial %} {% endblock %} @@ -198,4 +198,6 @@

    {% blocktrans %}Administration{% endblocktrans %}< {% endif %} + + {% include "misc/social_buttons.part.html" with link=tutorial.get_absolute_url_online text=tutorial.title %} {% endblock %} \ No newline at end of file diff --git a/update.md b/update.md index b56a381946..b062391650 100644 --- a/update.md +++ b/update.md @@ -1,9 +1,9 @@ -Ce fichier liste les actions faire pour mettre en production les diffrentes +Ce fichier liste les actions à faire pour mettre en production les différentes versions de Zeste de Savoir. -Ajoutez tout simplement vos instructions la suite de ce fichier. +Ajoutez tout simplement vos instructions à la suite de ce fichier. -Actions faire pour mettre en prod la version : v1.2 +Actions à faire pour mettre en prod la version : v1.2 ===================================================== Issue #1520 @@ -27,16 +27,74 @@ apt-get install optipng apt-get install jpegoptim ``` -Mettre jour le fichier `settings_prod.py` : +Mettre à jour le fichier `settings_prod.py` : ```python -THUMBNAIL_OPTIMIZE_COMMAND = { +THUMBNAIL_OPTIMIZE_COMMAND = { 'png': '/usr/bin/optipng {filename}', - 'gif': '/usr/bin/optipng {filename}', -'jpeg': '/usr/bin/jpegoptim {filename}' + 'gif': '/usr/bin/optipng {filename}', +'jpeg': '/usr/bin/jpegoptim {filename}' } ``` -Actions faire pour mettre en prod la version : v1.3 +Actions à faire pour mettre en prod la version : v1.3 ===================================================== + +Actions à faire pour mettre en prod la version : v1.4 +===================================================== + +Issue #381 +---------- + +1. Pour un compte **facebook** : + - allez sur https://developers.facebook.com/apps/?action=create et cliquer sur "Create New App" en vert + - Dans les paramètre de l'application crée cliquez sur “Add Platform”. Dans les options fournies, choisissez Web, et remplissez l'url du site avec "http://zestedesavoir.com" (adaptez l'adresse en fonction de l'adresse sur laquelle vous déployez) + - dans votre fichier `settings_prod.py` rajouter les variables `SOCIAL_AUTH_FACEBOOK_KEY = "clé"` +et `SOCIAL_AUTH_FACEBOOK_SECRET = "secret"` obtenu via l'application facebook + +2. Pour un compte **twitter** : + - allez sur https://apps.twitter.com/app/new et creez une nouvelle application + - remplissez les informations, et dans votre url de callback pensez à renseigner `http://zestedesavoir.com/complete/twitter/` (adaptez l'adresse en fonction de l'adresse sur laquelle vous déployez) + - dans votre fichier `settings_prod.py` rajouter les variables `SOCIAL_AUTH_TWITTER_KEY = "clé"` +et `SOCIAL_AUTH_TWITTER_SECRET = "secret"` obtenu via l'application twitter + +3. Pour un compte **google plus** : + - allez sur https://console.developers.google.com/ et creez une nouvelle application + - remplissez les informations, et dans votre url de callback pensez à renseigner `http://zestedesavoir.com/complete/google-oauth2/` (adaptez l'adrese en fonction de l'adresse sur laquelle vous testez déployez) + - dans votre fichier `settings_prod.py` rajouter les variables `SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = "clé"` et `SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = "secret"` obtenu via l'application google + +Issue #1487 +----------- + +Définir des positions pour les catégories de tutoriels dans la partie admin du site (par défaut toutes à 0). L'odre d'affichage se fait par ordre croissant. Les sous-catégories sont triées automatiquement par ordre alphabétique. + +ZEP-3 +----- + +#### Schéma + +Mettre à jour le modèle de données. +``` +python manage.py migrate +``` + +Si un souci apparait via South qui grogne avec un message comme celui-ci pas de panique ! +``` +Inconsistent migration history +The following options are available: + --merge: will just attempt the migration ignoring any potential dependency + conflicts. +``` +Il suffit de lui obéir et tout devrait rentrer dans l'ordre +``` +python manage.py migrate --merge +``` + +#### Données + +Pour la ZEP, il faut aussi générer les "aides" de base : rédacteur, correcteur, repreneur, illustrateur. +Tout est déjà prêt dans les fixtures dédiées à cela : +``` +python load_factory_data.py fixtures/advanced/aide_tuto_media.yaml +``` diff --git a/zds/article/factories.py b/zds/article/factories.py index e2e1ec92c3..ed43a8c49d 100644 --- a/zds/article/factories.py +++ b/zds/article/factories.py @@ -9,6 +9,7 @@ from zds.article.models import Article, Reaction, \ Validation, Licence from zds.utils.articles import export_article +from zds.article.views import mep class ArticleFactory(factory.DjangoModelFactory): @@ -61,7 +62,7 @@ def _prepare(cls, create, **kwargs): return reaction -class VaidationFactory(factory.DjangoModelFactory): +class ValidationFactory(factory.DjangoModelFactory): FACTORY_FOR = Validation @@ -75,3 +76,16 @@ class LicenceFactory(factory.DjangoModelFactory): def _prepare(cls, create, **kwargs): licence = super(LicenceFactory, cls)._prepare(create, **kwargs) return licence + + +class PublishedArticleFactory(ArticleFactory): + FACTORY_FOR = Article + + @classmethod + def _prepare(cls, create, **kwargs): + article = super(PublishedArticleFactory, cls)._prepare(create, **kwargs) + mep(article, article.sha_draft) + article.sha_public = article.sha_draft + article.pubdate = datetime.now() + article.save() + return article diff --git a/zds/article/forms.py b/zds/article/forms.py index b52aa91511..7e30aa373a 100644 --- a/zds/article/forms.py +++ b/zds/article/forms.py @@ -2,8 +2,9 @@ from django.conf import settings +from crispy_forms.bootstrap import StrictButton from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Field, Hidden +from crispy_forms.layout import Layout, Field, Hidden, ButtonHolder from django import forms from django.core.urlresolvers import reverse @@ -57,7 +58,14 @@ class ArticleForm(forms.Form): ) licence = forms.ModelChoiceField( - label=_(u"Licence de votre publication"), + label=_( + _(u'Licence de votre publication (En savoir plus sur les licences et {2})') + .format( + settings.ZDS_APP['site']['licenses']['licence_info_title'], + settings.ZDS_APP['site']['licenses']['licence_info_link'], + settings.ZDS_APP['site']['name'] + ) + ), queryset=Licence.objects.all(), required=True, empty_label=None @@ -172,3 +180,26 @@ def clean(self): u'caractères').format(settings.ZDS_APP['forum']['max_post_length'])]) return cleaned_data + + +class ActivJsForm(forms.Form): + + js_support = forms.BooleanField( + label='Cocher pour activer JSFiddle', + required=False, + initial=True + ) + + def __init__(self, *args, **kwargs): + super(ActivJsForm, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.form_action = reverse('zds.article.views.activ_js') + self.helper.form_method = 'post' + + self.helper.layout = Layout( + Field('js_support'), + ButtonHolder( + StrictButton( + _(u'Valider'), + type='submit'),), + Hidden('article', '{{ article.pk }}'), ) diff --git a/zds/article/migrations/0004_auto__add_field_article_js_support.py b/zds/article/migrations/0004_auto__add_field_article_js_support.py new file mode 100644 index 0000000000..0f8d0b7c21 --- /dev/null +++ b/zds/article/migrations/0004_auto__add_field_article_js_support.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Article.js_support' + db.add_column(u'article_article', 'js_support', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Article.js_support' + db.delete_column(u'article_article', 'js_support') + + + models = { + u'article.article': { + 'Meta': {'object_name': 'Article'}, + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}), + 'create_at': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'is_locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_visible': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'js_support': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_reaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_reaction'", 'null': 'True', 'to': u"orm['article.Reaction']"}), + 'licence': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Licence']", 'null': 'True', 'blank': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'sha_draft': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_public': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_validation': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subcategory': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['utils.SubCategory']", 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'article.articleread': { + 'Meta': {'object_name': 'ArticleRead'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['article.Article']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reaction': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['article.Reaction']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reactions_read'", 'to': u"orm['auth.User']"}) + }, + u'article.reaction': { + 'Meta': {'object_name': 'Reaction', '_ormbases': [u'utils.Comment']}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['article.Article']"}), + u'comment_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['utils.Comment']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'article.validation': { + 'Meta': {'object_name': 'Validation'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['article.Article']", 'null': 'True', 'blank': 'True'}), + 'comment_authors': ('django.db.models.fields.TextField', [], {}), + 'comment_validator': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_proposition': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'date_reserve': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'date_validation': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '10'}), + 'validator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'articles_author_validations'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'version': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}) + }, + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'utils.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': u"orm['auth.User']"}), + 'dislike': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments-editor'", 'null': 'True', 'to': u"orm['auth.User']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.CharField', [], {'max_length': '39'}), + 'is_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'like': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'position': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'text_hidden': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80'}), + 'text_html': ('django.db.models.fields.TextField', [], {}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'utils.licence': { + 'Meta': {'object_name': 'Licence'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'description': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.subcategory': { + 'Meta': {'object_name': 'SubCategory'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subtitle': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + } + } + + complete_apps = ['article'] \ No newline at end of file diff --git a/zds/article/models.py b/zds/article/models.py index 0ddef61969..2bccbb1a3f 100644 --- a/zds/article/models.py +++ b/zds/article/models.py @@ -13,10 +13,10 @@ try: import ujson as json_reader -except: +except ImportError: try: import simplejson as json_reader - except: + except ImportError: import json as json_reader import json as json_writer @@ -84,6 +84,7 @@ class Meta: related_name='last_reaction', verbose_name='Derniere réaction') is_locked = models.BooleanField('Est verrouillé', default=False) + js_support = models.BooleanField('Support du Javascript', default=False) licence = models.ForeignKey(Licence, verbose_name='Licence', @@ -141,7 +142,8 @@ def load_json(self, path=None, online=False): json_data = open(man_path) data = json_reader.load(json_data) json_data.close() - + if 'licence' in data: + data['licence'] = Licence.objects.get(code=data['licence']) return data else: return None @@ -150,7 +152,8 @@ def load_json_for_public(self): repo = Repo(self.get_path()) manarticle = get_blob(repo.commit(self.sha_public).tree, 'manifest.json') data = json_reader.loads(manarticle) - + if 'licence' in data: + data['licence'] = Licence.objects.get(code=data['licence']) return data def load_dic(self, article_version): @@ -298,10 +301,11 @@ def get_old_field_value(instance, field, manager): def get_last_articles(): + n = settings.ZDS_APP['article']['home_number'] return Article.objects.all()\ .exclude(sha_public__isnull=True)\ .exclude(sha_public__exact='')\ - .order_by('-pubdate')[:5] + .order_by('-pubdate')[:n] def get_prev_article(g_article): diff --git a/zds/article/tests/tests.py b/zds/article/tests/tests.py index ac4282562e..a0c5b288b4 100644 --- a/zds/article/tests/tests.py +++ b/zds/article/tests/tests.py @@ -426,7 +426,7 @@ def test_workflow_licence(self): # test change in JSON : json = article.load_json() - self.assertEquals(json['licence'], new_licence.code) + self.assertEquals(json['licence'].code, new_licence.code) # then logout ... self.client.logout() @@ -458,7 +458,7 @@ def test_workflow_licence(self): # test change in JSON : json = article.load_json() - self.assertEquals(json['licence'], self.licence.code) + self.assertEquals(json['licence'].code, self.licence.code) # then logout ... self.client.logout() @@ -511,7 +511,7 @@ def test_workflow_licence(self): # test change in JSON (normaly, nothing has) : json = article.load_json() - self.assertEquals(json['licence'], self.licence.code) + self.assertEquals(json['licence'].code, self.licence.code) def test_workflow_archive_article(self): """ensure the behavior of archive with an article""" diff --git a/zds/article/urls.py b/zds/article/urls.py index 40c472c366..0797113cef 100644 --- a/zds/article/urls.py +++ b/zds/article/urls.py @@ -14,7 +14,7 @@ url(r'^flux/atom/$', feeds.LastArticlesFeedATOM(), name='article-feed-atom'), - # TODO: Handle redirect + # Moderation url(r'^resolution_alerte/$', @@ -45,6 +45,8 @@ 'zds.article.views.reservation'), url(r'^validation/historique/(?P\d+)/$', 'zds.article.views.history_validation'), + url(r'^activation_js/$', + 'zds.article.views.activ_js'), # Reactions url(r'^message/editer/$', diff --git a/zds/article/views.py b/zds/article/views.py index 12b3463672..adda67d48f 100644 --- a/zds/article/views.py +++ b/zds/article/views.py @@ -4,12 +4,12 @@ from operator import attrgetter try: import ujson as json_reader -except: +except ImportError: try: import simplejson as json_reader - except: + except ImportError: import json as json_reader - +import json import json as json_writer import os import shutil @@ -26,14 +26,13 @@ from django.db import transaction from django.db.models import Q from django.http import Http404, HttpResponse -from django.shortcuts import get_object_or_404, redirect +from django.shortcuts import get_object_or_404, redirect, render from django.utils.encoding import smart_str from django.views.decorators.http import require_POST from git import Repo, Actor from zds.member.decorator import can_write_and_read_now from zds.member.views import get_client_ip -from zds.utils import render_template from zds.utils import slugify from zds.utils.articles import get_blob from zds.utils.mps import send_mp @@ -43,7 +42,7 @@ from zds.utils.tutorials import get_sep, get_text_is_empty from zds.utils.templatetags.emarkdown import emarkdown -from .forms import ArticleForm, ReactionForm +from .forms import ArticleForm, ReactionForm, ActivJsForm from .models import Article, get_prev_article, get_next_article, Validation, \ Reaction, never_read, mark_read @@ -76,7 +75,7 @@ def index(request): article_version = article.load_dic(article_version) article_versions.append(article_version) - return render_template('article/index.html', { + return render(request, 'article/index.html', { 'articles': article_versions, 'tag': tag, }) @@ -117,12 +116,20 @@ def view(request, article_pk, article_slug): .order_by("-date_proposition")\ .first() - return render_template('article/member/view.html', { + if article.js_support: + is_js = "js" + else: + is_js = "" + form_js = ActivJsForm(initial={"js_support": article.js_support}) + + return render(request, 'article/member/view.html', { 'article': article_version, 'authors': article.authors, 'tags': article.subcategory, 'version': sha, - 'validation': validation + 'validation': validation, + 'is_js': is_js, + 'formJs': form_js, }) @@ -194,7 +201,7 @@ def view_online(request, article_pk, article_slug): # Build form to send a reaction for the current article. form = ReactionForm(article, request.user) - return render_template('article/view.html', { + return render(request, 'article/view.html', { 'article': article_version, 'authors': article.authors, 'tags': article.subcategory, @@ -224,9 +231,10 @@ def new(request): 'text': request.POST['text'], 'image': image, 'subcategory': request.POST.getlist('subcategory'), - 'licence': request.POST['licence'] + 'licence': request.POST['licence'], + 'msg_commit': request.POST['msg_commit'] }) - return render_template('article/member/new.html', { + return render(request, 'article/member/new.html', { 'text': request.POST['text'], 'form': form }) @@ -283,7 +291,7 @@ def new(request): } ) - return render_template('article/member/new.html', { + return render(request, 'article/member/new.html', { 'form': form }) @@ -320,12 +328,15 @@ def edit(request): 'text': request.POST['text'], 'image': image, 'subcategory': request.POST.getlist('subcategory'), - 'licence': licence + 'licence': licence, + 'msg_commit': request.POST['msg_commit'] }) - return render_template('article/member/edit.html', { + form_js = ActivJsForm(initial={"js_support": article.js_support}) + return render(request, 'article/member/edit.html', { 'article': article, 'text': request.POST['text'], - 'form': form + 'form': form, + 'formJs': form_js }) form = ArticleForm(request.POST, request.FILES) @@ -368,7 +379,7 @@ def edit(request): return redirect(article.get_absolute_url()) else: if "licence" in json: - licence = Licence.objects.filter(code=json["licence"]).all()[0] + licence = json['licence'] else: licence = Licence.objects.get( pk=settings.ZDS_APP['tutorial']['default_license_pk'] @@ -381,8 +392,9 @@ def edit(request): 'licence': licence }) - return render_template('article/member/edit.html', { - 'article': article, 'form': form + form_js = ActivJsForm(initial={"js_support": article.js_support}) + return render(request, 'article/member/edit.html', { + 'article': article, 'form': form, 'formJs': form_js }) @@ -401,7 +413,7 @@ def find_article(request, pk_user): article_versions.append(article_version) # Paginator - return render_template('article/find.html', { + return render(request, 'article/find.html', { 'articles': article_versions, 'usr': user, }) @@ -850,7 +862,7 @@ def list_validation(request): article__subcategory__in=[subcategory]) \ .order_by("date_proposition") \ .all() - return render_template('article/validation/index.html', { + return render(request, 'article/validation/index.html', { 'validations': validations, }) @@ -881,7 +893,7 @@ def history_validation(request, article_pk): .order_by("date_proposition") \ .all() - return render_template('article/validation/history.html', { + return render(request, 'article/validation/history.html', { 'validations': validations, 'article': article, 'authors': article.authors, @@ -935,7 +947,7 @@ def history(request, article_pk, article_slug): logs = repo.head.reference.log() logs = sorted(logs, key=attrgetter('time'), reverse=True) - return render_template('article/member/history.html', { + return render(request, 'article/member/history.html', { 'article': article, 'logs': logs }) @@ -956,7 +968,11 @@ def mep(article, sha): article_version['text'] + '.html'), "w") - html_file.write(emarkdown(md_file_contenu)) + if article.js_support: + is_js = "js" + else: + is_js = "" + html_file.write(emarkdown(md_file_contenu, is_js)) html_file.close() @@ -1002,13 +1018,17 @@ def answer(request): form = ReactionForm(article, request.user, initial={ 'text': data['text'] }) - return render_template('article/reaction/new.html', { - 'article': article, - 'last_reaction_pk': last_reaction_pk, - 'newreaction': newreaction, - 'reactions': reactions, - 'form': form - }) + if request.is_ajax(): + return HttpResponse(json.dumps({"text": emarkdown(data["text"])}), + content_type='application/json') + else: + return render(request, 'article/reaction/new.html', { + 'article': article, + 'last_reaction_pk': last_reaction_pk, + 'newreaction': newreaction, + 'reactions': reactions, + 'form': form + }) # Saving the message else: @@ -1031,7 +1051,7 @@ def answer(request): return redirect(reaction.get_absolute_url()) else: - return render_template('article/reaction/new.html', { + return render(request, 'article/reaction/new.html', { 'article': article, 'last_reaction_pk': last_reaction_pk, 'newreaction': newreaction, @@ -1062,7 +1082,7 @@ def answer(request): form = ReactionForm(article, request.user, initial={ 'text': text }) - return render_template('article/reaction/new.html', { + return render(request, 'article/reaction/new.html', { 'article': article, 'reactions': reactions, 'last_reaction_pk': last_reaction_pk, @@ -1113,6 +1133,21 @@ def solve_alert(request): return redirect(reaction.get_absolute_url()) +@login_required +@require_POST +def activ_js(request): + + # only for staff + + if not request.user.has_perm("tutorial.change_tutorial"): + raise PermissionDenied + article = get_object_or_404(Article, pk=request.POST["article"]) + article.js_support = "js_support" in request.POST + article.save() + + return redirect(article.get_absolute_url()) + + @can_write_and_read_now @login_required def edit_reaction(request): @@ -1179,11 +1214,15 @@ def edit_reaction(request): 'zds.article.views.edit_reaction') + \ '?message=' + \ str(reaction_pk) - return render_template('article/reaction/edit.html', { - 'reaction': reaction, - 'article': g_article, - 'form': form - }) + if request.is_ajax(): + return HttpResponse(json.dumps({"text": emarkdown(request.POST["text"])}), + content_type='application/json') + else: + return render(request, 'article/reaction/edit.html', { + 'reaction': reaction, + 'article': g_article, + 'form': form + }) if 'delete_message' not in request.POST \ and 'signal_message' not in request.POST \ @@ -1205,7 +1244,7 @@ def edit_reaction(request): }) form.helper.form_action = reverse( 'zds.article.views.edit_reaction') + '?message=' + str(reaction_pk) - return render_template('article/reaction/edit.html', { + return render(request, 'article/reaction/edit.html', { 'reaction': reaction, 'article': g_article, 'form': form diff --git a/zds/forum/factories.py b/zds/forum/factories.py index 7ec2f295af..4b0a1ddb63 100644 --- a/zds/forum/factories.py +++ b/zds/forum/factories.py @@ -47,6 +47,7 @@ def _prepare(cls, create, **kwargs): post = super(PostFactory, cls)._prepare(create, **kwargs) topic = kwargs.pop('topic', None) if topic: + post.save() topic.last_message = post topic.save() return post diff --git a/zds/forum/models.py b/zds/forum/models.py index 93239ef780..d04129be78 100644 --- a/zds/forum/models.py +++ b/zds/forum/models.py @@ -108,7 +108,6 @@ def get_last_message(self): def can_read(self, user): """Checks if the forum can be read by the user.""" - # TODO These prints is used to debug this method. Remove them later. if self.group.count() == 0: return True @@ -231,7 +230,6 @@ def first_unread_post(self): topic__pk=self.pk, position__gt=last_post.position)\ .select_related("author").first() - return next_post except: return self.first_post() diff --git a/zds/forum/tests/tests.py b/zds/forum/tests/tests.py index 4ea6f1b8a6..d41510bcbc 100644 --- a/zds/forum/tests/tests.py +++ b/zds/forum/tests/tests.py @@ -259,6 +259,50 @@ def test_edit_main_post(self): # check edit data self.assertEqual(Post.objects.get(pk=post1.pk).editor, self.user) + # check if topic is valid (no topic) + result = self.client.post( + reverse('zds.forum.views.edit_post') + '?message={0}' + .format(post2.pk), + {'title': u'', + 'subtitle': u'Encore ces lombards en plein été', + 'text': u'C\'est tout simplement l\'histoire de la ville de Paris que je voudrais vous conter ' + }, + follow=False) + self.assertEqual(Topic.objects.get(pk=topic2.pk).title, topic2.title) + + # check if topic is valid (tags only) + result = self.client.post( + reverse('zds.forum.views.edit_post') + '?message={0}' + .format(post2.pk), + {'title': u'[foo][bar]', + 'subtitle': u'Encore ces lombards en plein été', + 'text': u'C\'est tout simplement l\'histoire de la ville de Paris que je voudrais vous conter ' + }, + follow=False) + self.assertEqual(Topic.objects.get(pk=topic2.pk).title, topic2.title) + + # check if topic is valid (spaces only) + result = self.client.post( + reverse('zds.forum.views.edit_post') + '?message={0}' + .format(post2.pk), + {'title': u' ', + 'subtitle': u'Encore ces lombards en plein été', + 'text': u'C\'est tout simplement l\'histoire de la ville de Paris que je voudrais vous conter ' + }, + follow=False) + self.assertEqual(Topic.objects.get(pk=topic2.pk).title, topic2.title) + + # check if topic is valid (valid title) + result = self.client.post( + reverse('zds.forum.views.edit_post') + '?message={0}' + .format(post2.pk), + {'title': u'Un titre valide', + 'subtitle': u'Encore ces lombards en plein été', + 'text': u'C\'est tout simplement l\'histoire de la ville de Paris que je voudrais vous conter ' + }, + follow=False) + self.assertEqual(Topic.objects.get(pk=topic2.pk).title, u'Un titre valide') + def test_edit_post(self): """To test all aspects of the edition of simple post by member.""" topic1 = TopicFactory(forum=self.forum11, author=self.user) diff --git a/zds/forum/views.py b/zds/forum/views.py index a0cc1626ac..e501cd3873 100644 --- a/zds/forum/views.py +++ b/zds/forum/views.py @@ -14,7 +14,7 @@ from django.core.urlresolvers import reverse from django.db import transaction from django.http import Http404, HttpResponse -from django.shortcuts import redirect, get_object_or_404 +from django.shortcuts import redirect, get_object_or_404, render from django.template import Context from django.template.loader import get_template from django.views.decorators.http import require_POST @@ -28,7 +28,7 @@ from zds.forum.models import TopicRead from zds.member.decorator import can_write_and_read_now from zds.member.views import get_client_ip -from zds.utils import render_template, slugify +from zds.utils import slugify from zds.utils.models import Alert, CommentLike, CommentDislike, Tag from zds.utils.mps import send_mp from zds.utils.paginator import paginator_range @@ -41,7 +41,7 @@ def index(request): categories = top_categories(request.user) - return render_template("forum/index.html", {"categories": categories, + return render(request, "forum/index.html", {"categories": categories, "user": request.user}) @@ -72,7 +72,7 @@ def details(request, cat_slug, forum_slug): shown_topics = paginator.page(paginator.num_pages) page = paginator.num_pages - return render_template("forum/category/forum.html", { + return render(request, "forum/category/forum.html", { "forum": forum, "sticky_topics": sticky_topics, "topics": shown_topics, @@ -101,7 +101,7 @@ def cat_details(request, cat_slug): else: forums = forums_pub - return render_template("forum/category/index.html", {"category": category, + return render(request, "forum/category/index.html", {"category": category, "forums": forums}) @@ -143,7 +143,7 @@ def topic(request, topic_pk, topic_slug): if "page" in request.GET: try: page_nbr = int(request.GET["page"]) - except: + except (KeyError, ValueError): # problem in variable format raise Http404 else: @@ -172,7 +172,7 @@ def topic(request, topic_pk, topic_slug): + str(topic.pk) form_move = MoveTopicForm(topic=topic) - return render_template("forum/topic/index.html", { + return render(request, "forum/topic/index.html", { "topic": topic, "posts": res, "categories": categories, @@ -226,7 +226,7 @@ def new(request): try: forum_pk = request.GET["forum"] - except: + except KeyError: # problem in variable format raise Http404 forum = get_object_or_404(Forum, pk=forum_pk) @@ -237,13 +237,18 @@ def new(request): # If the client is using the "preview" button if "preview" in request.POST: - form = TopicForm(initial={"title": request.POST["title"], - "subtitle": request.POST["subtitle"], - "text": request.POST["text"]}) - return render_template("forum/topic/new.html", - {"forum": forum, - "form": form, - "text": request.POST["text"]}) + if request.is_ajax(): + return HttpResponse(json.dumps({"text": emarkdown(request.POST["text"])}), + content_type='application/json') + else: + form = TopicForm(initial={"title": request.POST["title"], + "subtitle": request.POST["subtitle"], + "text": request.POST["text"]}) + + return render(request, "forum/topic/new.html", + {"forum": forum, + "form": form, + "text": request.POST["text"]}) form = TopicForm(request.POST) data = form.data if form.is_valid(): @@ -284,7 +289,7 @@ def new(request): else: form = TopicForm() - return render_template("forum/topic/new.html", {"forum": forum, "form": form}) + return render(request, "forum/topic/new.html", {"forum": forum, "form": form}) @can_write_and_read_now @@ -341,7 +346,7 @@ def move_topic(request): raise PermissionDenied try: topic_pk = request.GET["sujet"] - except: + except KeyError: # problem in variable format raise Http404 forum = get_object_or_404(Forum, pk=request.POST["forum"]) @@ -372,13 +377,13 @@ def edit(request): try: topic_pk = request.POST["topic"] - except: + except KeyError: # problem in variable format raise Http404 if "page" in request.POST: try: page = int(request.POST["page"]) - except: + except (KeyError, ValueError): # problem in variable format raise Http404 else: @@ -398,8 +403,6 @@ def edit(request): resp["solved"] = g_topic.is_solved if request.user.has_perm("forum.change_topic"): - # Staff actions using AJAX TODO: Do not redirect on AJAX requests - if "lock" in data: g_topic.is_locked = data["lock"] == "true" messages.success(request, @@ -413,14 +416,14 @@ def edit(request): if "move" in data: try: forum_pk = int(request.POST["move_target"]) - except: + except (KeyError, ValueError): # problem in variable format raise Http404 forum = get_object_or_404(Forum, pk=forum_pk) g_topic.forum = forum g_topic.save() if request.is_ajax(): - return HttpResponse(json.dumps(resp)) + return HttpResponse(json.dumps(resp), content_type='application/json') else: if not g_topic.forum.can_read(request.user): return redirect(reverse("zds.forum.views.index")) @@ -437,7 +440,7 @@ def answer(request): try: topic_pk = request.GET["sujet"] - except: + except KeyError: # problem in variable format raise Http404 @@ -475,14 +478,18 @@ def answer(request): form = PostForm(g_topic, request.user, initial={"text": data["text"]}) form.helper.form_action = reverse("zds.forum.views.answer") \ + "?sujet=" + str(g_topic.pk) - return render_template("forum/post/new.html", { - "text": data["text"], - "topic": g_topic, - "posts": posts, - "last_post_pk": last_post_pk, - "newpost": newpost, - "form": form, - }) + if request.is_ajax(): + return HttpResponse(json.dumps({"text": emarkdown(request.POST["text"])}), + content_type='application/json') + else: + return render(request, "forum/post/new.html", { + "text": data["text"], + "topic": g_topic, + "posts": posts, + "last_post_pk": last_post_pk, + "newpost": newpost, + "form": form, + }) else: # Saving the message @@ -543,7 +550,7 @@ def answer(request): follow(g_topic) return redirect(post.get_absolute_url()) else: - return render_template("forum/post/new.html", { + return render(request, "forum/post/new.html", { "text": data["text"], "topic": g_topic, "posts": posts, @@ -560,6 +567,7 @@ def answer(request): # Using the quote button if "cite" in request.GET: + resp = {} post_cite_pk = request.GET["cite"] post_cite = Post.objects.get(pk=post_cite_pk) if not post_cite.is_visible: @@ -572,10 +580,14 @@ def answer(request): settings.ZDS_APP['site']['url'], post_cite.get_absolute_url()) + if request.is_ajax(): + resp["text"] = text + return HttpResponse(json.dumps(resp), content_type='application/json') + form = PostForm(g_topic, request.user, initial={"text": text}) form.helper.form_action = reverse("zds.forum.views.answer") \ + "?sujet=" + str(g_topic.pk) - return render_template("forum/post/new.html", { + return render(request, "forum/post/new.html", { "topic": g_topic, "posts": posts, "last_post_pk": last_post_pk, @@ -591,7 +603,7 @@ def edit_post(request): try: post_pk = request.GET["message"] - except: + except KeyError: # problem in variable format raise Http404 post = get_object_or_404(Post, pk=post_pk) @@ -645,27 +657,42 @@ def edit_post(request): # Using the preview button if "preview" in request.POST: - if g_topic: - form = TopicForm(initial={"title": request.POST["title"], - "subtitle": request.POST["subtitle"], - "text": request.POST["text"]}) + if request.is_ajax(): + return HttpResponse(json.dumps({"text": emarkdown(request.POST["text"])}), + content_type='application/json') else: - form = PostForm(post.topic, request.user, - initial={"text": request.POST["text"]}) - form.helper.form_action = reverse("zds.forum.views.edit_post") \ - + "?message=" + str(post_pk) - return render_template("forum/post/edit.html", { - "post": post, - "topic": post.topic, - "text": request.POST["text"], - "form": form, - }) + if g_topic: + form = TopicForm(initial={"title": request.POST["title"], + "subtitle": request.POST["subtitle"], + "text": request.POST["text"]}) + else: + form = PostForm(post.topic, request.user, + initial={"text": request.POST["text"]}) + + form.helper.form_action = reverse("zds.forum.views.edit_post") \ + + "?message=" + str(post_pk) + + return render(request, "forum/post/edit.html", { + "post": post, + "topic": post.topic, + "text": request.POST["text"], + "form": form, + }) if "delete_message" not in request.POST and "signal_message" \ not in request.POST and "show_message" not in request.POST: # The user just sent data, handle them if request.POST["text"].strip() != "": + # check if the form is valid + form = TopicForm(request.POST) + if not form.is_valid() and g_topic: + return render(request, "forum/post/edit.html", { + "post": post, + "topic": post.topic, + "text": post.text, + "form": form, + }) post.text = request.POST["text"] post.text_html = emarkdown(request.POST["text"]) post.update = datetime.now() @@ -702,7 +729,7 @@ def edit_post(request): initial={"text": post.text}) form.helper.form_action = reverse("zds.forum.views.edit_post") \ + "?message=" + str(post_pk) - return render_template("forum/post/edit.html", { + return render(request, "forum/post/edit.html", { "post": post, "topic": post.topic, "text": post.text, @@ -718,7 +745,7 @@ def useful_post(request): try: post_pk = request.GET["message"] - except: + except KeyError: # problem in variable format raise Http404 post = get_object_or_404(Post, pk=post_pk) @@ -745,7 +772,7 @@ def unread_post(request): try: post_pk = request.GET["message"] - except: + except KeyError: # problem in variable format raise Http404 post = get_object_or_404(Post, pk=post_pk) @@ -754,6 +781,8 @@ def unread_post(request): if not post.topic.forum.can_read(request.user): raise PermissionDenied + if TopicFollowed.objects.filter(user=request.user, topic=post.topic).count() == 0: + TopicFollowed(user=request.user, topic=post.topic).save() t = TopicRead.objects.filter(topic=post.topic, user=request.user).first() if t is None: @@ -780,7 +809,7 @@ def like_post(request): try: post_pk = request.GET["message"] - except: + except KeyError: # problem in variable format raise Http404 resp = {} @@ -828,7 +857,7 @@ def dislike_post(request): try: post_pk = request.GET["message"] - except: + except KeyError: # problem in variable format raise Http404 resp = {} @@ -913,7 +942,7 @@ def find_topic_by_tag(request, tag_pk, tag_slug): except EmptyPage: shown_topics = paginator.page(paginator.num_pages) page = paginator.num_pages - return render_template("forum/find/topic_by_tag.html", { + return render(request, "forum/find/topic_by_tag.html", { "topics": shown_topics, "tag": tag, "pages": paginator_range(page, paginator.num_pages), @@ -946,7 +975,7 @@ def find_topic(request, user_pk): shown_topics = paginator.page(paginator.num_pages) page = paginator.num_pages - return render_template("forum/find/topic.html", { + return render(request, "forum/find/topic.html", { "topics": shown_topics, "usr": displayed_user, "pages": paginator_range(page, paginator.num_pages), @@ -986,7 +1015,7 @@ def find_post(request, user_pk): shown_posts = paginator.page(paginator.num_pages) page = paginator.num_pages - return render_template("forum/find/post.html", { + return render(request, "forum/find/post.html", { "posts": shown_posts, "usr": displayed_user, "pages": paginator_range(page, paginator.num_pages), @@ -1011,7 +1040,7 @@ def followed_topics(request): except EmptyPage: shown_topics = paginator.page(paginator.num_pages) page = paginator.num_pages - return render_template("forum/topic/followed.html", + return render(request, "forum/topic/followed.html", {"followed_topics": shown_topics, "pages": paginator_range(page, paginator.num_pages), diff --git a/zds/gallery/models.py b/zds/gallery/models.py index c691c3ed6e..3e2524aa8c 100644 --- a/zds/gallery/models.py +++ b/zds/gallery/models.py @@ -117,8 +117,8 @@ def get_gallery_path(self): """get the physical path to this gallery root""" return os.path.join(MEDIA_ROOT, 'galleries', str(self.pk)) - # TODO rename function to get_users_galleries - def get_users(self): + def get_linked_users(self): + """get all the linked users for this gallery whatever their rights""" return UserGallery.objects.all()\ .filter(gallery=self) diff --git a/zds/gallery/tests/tests_models.py b/zds/gallery/tests/tests_models.py index 33f81bf05b..b41a9d0ec2 100644 --- a/zds/gallery/tests/tests_models.py +++ b/zds/gallery/tests/tests_models.py @@ -107,9 +107,9 @@ def test_get_absolute_url(self): args=[self.gallery.pk, self.gallery.slug]) self.assertEqual(absolute_url, self.gallery.get_absolute_url()) - def test_get_users(self): - self.assertEqual(1, len(self.gallery.get_users())) - self.assertEqual(self.user_gallery, self.gallery.get_users()[0]) + def test_get_linked_users(self): + self.assertEqual(1, len(self.gallery.get_linked_users())) + self.assertEqual(self.user_gallery, self.gallery.get_linked_users()[0]) def test_get_images(self): self.assertEqual(2, len(self.gallery.get_images())) diff --git a/zds/gallery/views.py b/zds/gallery/views.py index dd417fd3bd..170485d590 100644 --- a/zds/gallery/views.py +++ b/zds/gallery/views.py @@ -11,12 +11,11 @@ from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse -from django.shortcuts import redirect, get_object_or_404 +from django.shortcuts import redirect, get_object_or_404, render from zds.gallery.forms import ArchiveImageForm, ImageForm, UpdateImageForm, \ GalleryForm, UserGalleryForm, ImageAsAvatarForm from zds.gallery.models import UserGallery, Image, Gallery from zds.member.decorator import can_write_and_read_now -from zds.utils import render_template from zds.utils import slugify from django.core.exceptions import ObjectDoesNotExist @@ -34,7 +33,7 @@ def gallery_list(request): """Display the gallery list with all their images.""" galleries = UserGallery.objects.all().filter(user=request.user) - return render_template("gallery/gallery/list.html", + return render(request, "gallery/gallery/list.html", {"galleries": galleries}) @@ -50,7 +49,7 @@ def gallery_details(request, gal_pk, gal_slug): images = gal.get_images() form = UserGalleryForm() - return render_template("gallery/gallery/details.html", { + return render(request, "gallery/gallery/details.html", { "gallery": gal, "gallery_mode": gal_mode, "images": images, @@ -86,10 +85,10 @@ def new_gallery(request): userg.save() return redirect(gal.get_absolute_url()) else: - return render_template("gallery/gallery/new.html", {"form": form}) + return render(request, "gallery/gallery/new.html", {"form": form}) else: form = GalleryForm() - return render_template("gallery/gallery/new.html", {"form": form}) + return render(request, "gallery/gallery/new.html", {"form": form}) @can_write_and_read_now @@ -170,7 +169,7 @@ def modify_gallery(request): ug.mode = request.POST["mode"] ug.save() else: - return render_template("gallery/gallery/details.html", { + return render(request, "gallery/gallery/details.html", { "gallery": gallery, "gallery_mode": gal_mode, "images": gallery.get_images(), @@ -236,7 +235,8 @@ def edit_image(request, gal_pk, img_pk): }) as_avatar_form = ImageAsAvatarForm() - return render_template( + return render( + request, "gallery/image/edit.html", { "form": form, "gallery_mode": gal_mode, @@ -269,6 +269,8 @@ def delete_image(request): try: img = Image.objects.get(pk=request.POST["image"], gallery=gal) img.delete() + except KeyError: + pass except: pass elif "delete_multi" in request.POST: @@ -309,12 +311,12 @@ def new_image(request, gal_pk): return redirect(reverse("zds.gallery.views.edit_image", args=[gal.pk, img.pk])) else: - return render_template("gallery/image/new.html", {"form": form, + return render(request, "gallery/image/new.html", {"form": form, "gallery_mode": gal_mode, "gallery": gal}) else: form = ImageForm(initial={"new_image": True}) # A empty, unbound form - return render_template("gallery/image/new.html", {"form": form, + return render(request, "gallery/image/new.html", {"form": form, "gallery_mode": gal_mode, "gallery": gal}) @@ -392,11 +394,11 @@ def import_image(request, gal_pk): return redirect(reverse("zds.gallery.views.gallery_details", args=[gal.pk, gal.slug])) else: - return render_template("gallery/image/new.html", {"form": form, + return render(request, "gallery/image/new.html", {"form": form, "gallery_mode": gal_mode, "gallery": gal}) else: form = ArchiveImageForm(initial={"new_image": True}) # A empty, unbound form - return render_template("gallery/image/new.html", {"form": form, + return render(request, "gallery/image/new.html", {"form": form, "gallery_mode": gal_mode, "gallery": gal}) diff --git a/zds/member/factories.py b/zds/member/factories.py index 609968a267..5a0b80fe60 100644 --- a/zds/member/factories.py +++ b/zds/member/factories.py @@ -1,6 +1,6 @@ # coding: utf-8 -from django.contrib.auth.models import User, Permission +from django.contrib.auth.models import User, Permission, Group import factory from zds.member.models import Profile @@ -50,13 +50,14 @@ def _prepare(cls, create, **kwargs): user.set_password(password) if create: user.save() - perms = Permission.objects.filter(codename__startswith='change_').all() + group_staff = Group.objects.filter(name="staff").first() + if group_staff is None: + group_staff = Group(name="staff") + group_staff.save() - user.user_permissions = list(perms) - user.user_permissions.add( - Permission.objects.get( - codename='moderation')) - user.user_permissions.add(Permission.objects.get(codename='show_ip')) + perms = Permission.objects.filter(codename__startswith='change_').all() + group_staff.permissions = perms + user.groups.add(group_staff) user.save() return user diff --git a/zds/member/forms.py b/zds/member/forms.py index c66e3ebab2..b863896b43 100644 --- a/zds/member/forms.py +++ b/zds/member/forms.py @@ -27,7 +27,7 @@ class OldTutoForm(forms.Form): id = forms.ChoiceField( - label=_('Ancien Tutoriel'), + label=_(u'Ancien Tutoriel'), required=True, choices=listing(), ) @@ -43,14 +43,14 @@ def __init__(self, profile, *args, **kwargs): Field('id'), Hidden('profile_pk', '{{ profile.pk }}'), ButtonHolder( - StrictButton(_('Attribuer'), type='submit'), + StrictButton(_(u'Attribuer'), type='submit'), ), ) class LoginForm(forms.Form): username = forms.CharField( - label=_("Nom d'utilisateur"), + label=_(u"Nom d'utilisateur"), max_length=User._meta.get_field('username').max_length, required=True, widget=forms.TextInput( @@ -61,7 +61,7 @@ class LoginForm(forms.Form): ) password = forms.CharField( - label=_('Mot de passe'), + label=_(u'Mot de passe'), max_length=MAX_PASSWORD_LENGTH, min_length=MIN_PASSWORD_LENGTH, required=True, @@ -69,7 +69,7 @@ class LoginForm(forms.Form): ) remember = forms.BooleanField( - label=_('Se souvenir de moi'), + label=_(u'Se souvenir de moi'), initial=True, ) @@ -78,6 +78,7 @@ def __init__(self, next=None, *args, **kwargs): self.helper = FormHelper() self.helper.form_action = reverse('zds.member.views.login_view') self.helper.form_method = 'post' + self.helper.form_class = 'content-wrapper' self.helper.layout = Layout( Field('username'), @@ -85,26 +86,28 @@ def __init__(self, next=None, *args, **kwargs): Field('remember'), HTML('{% csrf_token %}'), ButtonHolder( - StrictButton(_('Se connecter'), type='submit'), + StrictButton(_(u'Se connecter'), type='submit'), ), + HTML(u'Mot de passe oublié ?'), ) class RegisterForm(forms.Form): email = forms.EmailField( - label=_('Adresse courriel'), + label=_(u'Adresse courriel'), max_length=User._meta.get_field('email').max_length, required=True, ) username = forms.CharField( - label=_('Nom d\'utilisateur'), + label=_(u'Nom d\'utilisateur'), max_length=User._meta.get_field('username').max_length, required=True, ) password = forms.CharField( - label=_('Mot de passe'), + label=_(u'Mot de passe'), max_length=MAX_PASSWORD_LENGTH, min_length=MIN_PASSWORD_LENGTH, required=True, @@ -112,7 +115,7 @@ class RegisterForm(forms.Form): ) password_confirm = forms.CharField( - label=_('Confirmation du mot de passe'), + label=_(u'Confirmation du mot de passe'), max_length=MAX_PASSWORD_LENGTH, min_length=MIN_PASSWORD_LENGTH, required=True, @@ -131,7 +134,7 @@ def __init__(self, *args, **kwargs): Field('password_confirm'), Field('email'), ButtonHolder( - Submit('submit', _('Valider mon inscription')), + Submit('submit', _(u'Valider mon inscription')), )) def clean(self): @@ -204,7 +207,7 @@ class MiniProfileForm(forms.Form): required=False, widget=forms.Textarea( attrs={ - 'placeholder': _('Votre biographie au format Markdown.') + 'placeholder': _(u'Votre biographie au format Markdown.') } ) ) @@ -239,7 +242,7 @@ class MiniProfileForm(forms.Form): max_length=Profile._meta.get_field('sign').max_length, widget=forms.TextInput( attrs={ - 'placeholder': _('Elle apparaitra dans les messages de forums. ') + 'placeholder': _(u'Elle apparaitra dans les messages de forums. ') } ) ) @@ -266,9 +269,9 @@ class ProfileForm(MiniProfileForm): label='', required=False, choices=( - ('show_email', _("Afficher mon adresse courriel publiquement")), - ('show_sign', _("Afficher les signatures")), - ('hover_or_click', _("Cochez pour dérouler les menus au survol")), + ('show_email', _(u"Afficher mon adresse courriel publiquement")), + ('show_sign', _(u"Afficher les signatures")), + ('hover_or_click', _(u"Cochez pour dérouler les menus au survol")), ('email_for_answer', _(u'Recevez un courriel lorsque vous ' u'recevez une réponse à un message privé')), ), @@ -317,19 +320,19 @@ def __init__(self, *args, **kwargs): class ChangeUserForm(forms.Form): username_new = forms.CharField( - label=_('Nouveau pseudo'), + label=_(u'Nouveau pseudo'), max_length=User._meta.get_field('username').max_length, min_length=1, required=False, widget=forms.TextInput( attrs={ - 'placeholder': _('Ne mettez rien pour conserver l\'ancien') + 'placeholder': _(u'Ne mettez rien pour conserver l\'ancien') } ) ) email_new = forms.EmailField( - label=_('Nouvelle adresse courriel'), + label=_(u'Nouvelle adresse courriel'), max_length=User._meta.get_field('email').max_length, required=False, widget=forms.TextInput( @@ -350,7 +353,7 @@ def __init__(self, *args, **kwargs): Field('username_new'), Field('email_new'), ButtonHolder( - StrictButton(_('Enregistrer'), type='submit'), + StrictButton(_(u'Enregistrer'), type='submit'), ), ) @@ -393,21 +396,21 @@ def clean(self): # to update a password class ChangePasswordForm(forms.Form): password_new = forms.CharField( - label=_('Nouveau mot de passe'), + label=_(u'Nouveau mot de passe'), max_length=MAX_PASSWORD_LENGTH, min_length=MIN_PASSWORD_LENGTH, widget=forms.PasswordInput ) password_old = forms.CharField( - label=_('Mot de passe actuel'), + label=_(u'Mot de passe actuel'), max_length=MAX_PASSWORD_LENGTH, min_length=MIN_PASSWORD_LENGTH, widget=forms.PasswordInput ) password_confirm = forms.CharField( - label=_('Confirmer le nouveau mot de passe'), + label=_(u'Confirmer le nouveau mot de passe'), max_length=MAX_PASSWORD_LENGTH, min_length=MIN_PASSWORD_LENGTH, widget=forms.PasswordInput @@ -426,7 +429,7 @@ def __init__(self, user, *args, **kwargs): Field('password_new'), Field('password_confirm'), ButtonHolder( - StrictButton(_('Enregistrer'), type='submit'), + StrictButton(_(u'Enregistrer'), type='submit'), ) ) @@ -477,7 +480,7 @@ def clean(self): # Reset the password class ForgotPasswordForm(forms.Form): username = forms.CharField( - label=_('Nom d\'utilisateur'), + label=_(u'Nom d\'utilisateur'), max_length=User._meta.get_field('username').max_length, required=True ) @@ -491,7 +494,7 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( Field('username'), ButtonHolder( - StrictButton(_('Envoyer'), type='submit'), + StrictButton(_(u'Envoyer'), type='submit'), ) ) @@ -510,13 +513,13 @@ def clean(self): class NewPasswordForm(forms.Form): password = forms.CharField( - label=_('Mot de passe'), + label=_(u'Mot de passe'), max_length=MAX_PASSWORD_LENGTH, min_length=MIN_PASSWORD_LENGTH, widget=forms.PasswordInput ) password_confirm = forms.CharField( - label=_('Confirmation'), + label=_(u'Confirmation'), max_length=MAX_PASSWORD_LENGTH, min_length=MIN_PASSWORD_LENGTH, widget=forms.PasswordInput @@ -533,7 +536,7 @@ def __init__(self, identifier, *args, **kwargs): Field('password'), Field('password_confirm'), ButtonHolder( - StrictButton(_('Envoyer'), type='submit'), + StrictButton(_(u'Envoyer'), type='submit'), ) ) @@ -570,18 +573,18 @@ def clean(self): class PromoteMemberForm(forms.Form): groups = forms.ModelMultipleChoiceField( - label=_("Groupe de l'utilisateur"), + label=_(u"Groupe de l'utilisateur"), queryset=Group.objects.all(), required=False, ) superuser = forms.BooleanField( - label=_("Super-user"), + label=_(u"Super-user"), required=False, ) activation = forms.BooleanField( - label=_("Compte actif"), + label=_(u"Compte actif"), required=False, ) @@ -595,7 +598,7 @@ def __init__(self, *args, **kwargs): Field('groups'), Field('superuser'), Field('activation'), - StrictButton(_('Valider'), type='submit'), + StrictButton(_(u'Valider'), type='submit'), ) @@ -604,7 +607,7 @@ class KarmaForm(forms.Form): max_length=KarmaNote._meta.get_field('comment').max_length, widget=forms.TextInput( attrs={ - 'placeholder': 'Commentaire sur le comportement de ce membre' + 'placeholder': u'Commentaire sur le comportement de ce membre' }), required=True, ) @@ -628,6 +631,6 @@ def __init__(self, profile, *args, **kwargs): Field('points'), Hidden('profile_pk', '{{ profile.pk }}'), ButtonHolder( - StrictButton('Valider', type='submit'), + StrictButton(u'Valider', type='submit'), ), ) diff --git a/zds/member/models.py b/zds/member/models.py index 1801cb71f5..7f34352172 100644 --- a/zds/member/models.py +++ b/zds/member/models.py @@ -271,6 +271,18 @@ def __unicode__(self): return u"{0} - {1}".format(self.user.username, self.date_end) +def save_profile(backend, user, response, *args, **kwargs): + profile = Profile.objects.filter(user=user).first() + if profile is None: + profile = Profile(user=user, + show_email=False, + show_sign=True, + hover_or_click=True, + email_for_answer=False) + profile.last_ip_address = "0.0.0.0" + profile.save() + + class Ban(models.Model): class Meta: diff --git a/zds/member/tests/tests_views.py b/zds/member/tests/tests_views.py index 64df73f2dc..4de90c80dd 100644 --- a/zds/member/tests/tests_views.py +++ b/zds/member/tests/tests_views.py @@ -17,13 +17,12 @@ from zds.member.models import Profile, KarmaNote from zds.mp.models import PrivatePost, PrivateTopic from zds.member.models import TokenRegister, Ban -from zds.tutorial.factories import MiniTutorialFactory -from zds.tutorial.models import Tutorial, Validation -from zds.article.factories import ArticleFactory +from zds.tutorial.factories import MiniTutorialFactory, PublishedMiniTutorial +from zds.tutorial.models import Tutorial +from zds.article.factories import ArticleFactory, PublishedArticleFactory from zds.article.models import Article from zds.forum.factories import CategoryFactory, ForumFactory, TopicFactory, PostFactory from zds.forum.models import Topic, Post -from zds.article.models import Validation as ArticleValidation from zds.gallery.factories import GalleryFactory, UserGalleryFactory from zds.gallery.models import Gallery, UserGallery @@ -66,8 +65,26 @@ def test_login(self): 'password': 'hostel77', 'remember': 'remember'}, follow=False) - # good password then redirection - self.assertEqual(result.status_code, 302) + # good password then redirection to the homepage + self.assertRedirects(result, reverse('zds.pages.views.home')) + + result = self.client.post( + reverse('zds.member.views.login_view') + + '?next=' + reverse('zds.gallery.views.gallery_list'), + {'username': user.user.username, + 'password': 'hostel77', + 'remember': 'remember'}, + follow=False) + # good password and ?next= then redirection to the "next" page + self.assertRedirects(result, reverse('zds.gallery.views.gallery_list')) + + self.client.logout() + result = self.client.get(reverse('zds.member.views.login_view') + + '?next=' + reverse('zds.gallery.views.gallery_list')) + # check if the login form will redirect if there is a ?next= + self.assertContains(result, reverse('zds.member.views.login_view') + + '?next=' + reverse('zds.gallery.views.gallery_list'), + count=1) def test_register(self): """To test user registration.""" @@ -127,11 +144,11 @@ def test_unregister(self): UserGalleryFactory(gallery=sharedGallery, user=user.user) UserGalleryFactory(gallery=sharedGallery, user=user2.user) # first case : a published tutorial with only one author - publishedTutorialAlone = MiniTutorialFactory(light=True) + publishedTutorialAlone = PublishedMiniTutorial(light=True) publishedTutorialAlone.authors.add(user.user) publishedTutorialAlone.save() # second case : a published tutorial with two authors - publishedTutorial2 = MiniTutorialFactory(light=True) + publishedTutorial2 = PublishedMiniTutorial(light=True) publishedTutorial2.authors.add(user.user) publishedTutorial2.authors.add(user2.user) publishedTutorial2.save() @@ -147,65 +164,11 @@ def test_unregister(self): writingTutorial2.authors.add(user2.user) writingTutorial2.save() self.client.login(username=self.staff.username, password="hostel77") - pub = self.client.post( - reverse('zds.tutorial.views.ask_validation'), - { - 'tutorial': publishedTutorialAlone.pk, - 'text': u'Ce tuto est excellent', - 'version': publishedTutorialAlone.sha_draft, - 'source': 'http://zestedesavoir.com', - }, - follow=False) - self.assertEqual(pub.status_code, 302) - # reserve tutorial - validation = Validation.objects.get( - tutorial__pk=publishedTutorialAlone.pk) - pub = self.client.post( - reverse('zds.tutorial.views.reservation', args=[validation.pk]), - follow=False) - self.assertEqual(pub.status_code, 302) - # publish tutorial - pub = self.client.post( - reverse('zds.tutorial.views.valid_tutorial'), - { - 'tutorial': publishedTutorialAlone.pk, - 'text': u'Ce tuto est excellent', - 'is_major': True, - 'source': 'http://zestedesavoir.com', - }, - follow=False) - pub = self.client.post( - reverse('zds.tutorial.views.ask_validation'), - { - 'tutorial': publishedTutorial2.pk, - 'text': u'Ce tuto est excellent', - 'version': publishedTutorial2.sha_draft, - 'source': 'http://zestedesavoir.com', - }, - follow=False) - self.assertEqual(pub.status_code, 302) - # reserve tutorial - validation = Validation.objects.get( - tutorial__pk=publishedTutorial2.pk) - pub = self.client.post( - reverse('zds.tutorial.views.reservation', args=[validation.pk]), - follow=False) - self.assertEqual(pub.status_code, 302) - # publish tutorial - pub = self.client.post( - reverse('zds.tutorial.views.valid_tutorial'), - { - 'tutorial': publishedTutorial2.pk, - 'text': u'Ce tuto est excellent', - 'is_major': True, - 'source': 'http://zestedesavoir.com', - }, - follow=False) # same thing for articles - publishedArticleAlone = ArticleFactory() + publishedArticleAlone = PublishedArticleFactory() publishedArticleAlone.authors.add(user.user) publishedArticleAlone.save() - publishedArticle2 = ArticleFactory() + publishedArticle2 = PublishedArticleFactory() publishedArticle2.authors.add(user.user) publishedArticle2.authors.add(user2.user) publishedArticle2.save() @@ -217,80 +180,6 @@ def test_unregister(self): writingArticle2.authors.add(user.user) writingArticle2.authors.add(user2.user) writingArticle2.save() - # ask public article - pub = self.client.post( - reverse('zds.article.views.modify'), - { - 'article': publishedArticleAlone.pk, - 'comment': u'Valides moi ce bébé', - 'pending': 'Demander validation', - 'version': publishedArticleAlone.sha_draft, - 'is_major': True - }, - follow=False) - self.assertEqual(pub.status_code, 302) - - login_check = self.client.login( - username=self.staff.username, - password='hostel77') - self.assertEqual(login_check, True) - - # reserve article - validation = ArticleValidation.objects.get( - article__pk=publishedArticleAlone.pk) - pub = self.client.post( - reverse('zds.article.views.reservation', args=[validation.pk]), - follow=False) - self.assertEqual(pub.status_code, 302) - - # publish article - pub = self.client.post( - reverse('zds.article.views.modify'), - { - 'article': publishedArticleAlone.pk, - 'comment-v': u'Cet article est excellent', - 'valid-article': 'Demander validation', - 'is_major': True - }, - follow=False) - self.assertEqual(pub.status_code, 302) - # ask public article - pub = self.client.post( - reverse('zds.article.views.modify'), - { - 'article': publishedArticle2.pk, - 'comment': u'Valides moi ce bébé', - 'pending': 'Demander validation', - 'version': publishedArticle2.sha_draft, - 'is_major': True - }, - follow=False) - self.assertEqual(pub.status_code, 302) - - login_check = self.client.login( - username=self.staff.username, - password='hostel77') - self.assertEqual(login_check, True) - - # reserve article - validation = ArticleValidation.objects.get( - article__pk=publishedArticle2.pk) - pub = self.client.post( - reverse('zds.article.views.reservation', args=[validation.pk]), - follow=False) - self.assertEqual(pub.status_code, 302) - - # publish article - pub = self.client.post( - reverse('zds.article.views.modify'), - { - 'article': publishedArticle2.pk, - 'comment-v': u'Cet article est excellent', - 'valid-article': 'Demander validation', - 'is_major': True - }, - follow=False) - self.assertEqual(pub.status_code, 302) # about posts and topics authoredTopic = TopicFactory(author=user.user, forum=self.forum11) answeredTopic = TopicFactory(author=user2.user, forum=self.forum11) @@ -369,8 +258,8 @@ def test_unregister(self): self.assertIsNotNone(Topic.objects.get(pk=authoredTopic.pk)) self.assertIsNotNone(PrivateTopic.objects.get(pk=privateTopic.pk)) self.assertIsNotNone(Gallery.objects.get(pk=aloneGallery.pk)) - self.assertEquals(aloneGallery.get_users().count(), 1) - self.assertEquals(sharedGallery.get_users().count(), 1) + self.assertEquals(aloneGallery.get_linked_users().count(), 1) + self.assertEquals(sharedGallery.get_linked_users().count(), 1) self.assertEquals(UserGallery.objects.filter(user=user.user).count(), 0) def test_sanctions(self): diff --git a/zds/member/views.py b/zds/member/views.py index ac6ca3b75c..5e1afaa6d8 100644 --- a/zds/member/views.py +++ b/zds/member/views.py @@ -16,7 +16,7 @@ from django.db import transaction from django.db.models import Q from django.http import Http404, HttpResponse -from django.shortcuts import redirect, get_object_or_404 +from django.shortcuts import redirect, get_object_or_404, render from django.template import Context from django.template.loader import get_template from django.views.decorators.http import require_POST @@ -36,7 +36,6 @@ from zds.forum.models import Topic, follow, TopicFollowed from zds.member.decorator import can_write_and_read_now from zds.tutorial.models import Tutorial -from zds.utils import render_template from zds.utils.mps import send_mp from zds.utils.paginator import paginator_range from zds.utils.tokens import generate_token @@ -80,7 +79,7 @@ def index(request): except EmptyPage: shown_members = paginator.page(paginator.num_pages) page = paginator.num_pages - return render_template("member/index.html", { + return render(request, "member/index.html", { "members": shown_members, "count": members.count(), "pages": paginator_range(page, paginator.num_pages), @@ -91,7 +90,7 @@ def index(request): @login_required def warning_unregister(request): """displays a warning page showing what will happen when user unregisters""" - return render_template("member/settings/unregister.html", {"user": request.user}) + return render(request, "member/settings/unregister.html", {"user": request.user}) @login_required @@ -169,7 +168,7 @@ def unregister(request): # so we will just delete the unretistering user ownership and give it to anonymous in the only case # he was alone so that gallery is not lost for gallery in UserGallery.objects.filter(user=current): - if gallery.gallery.get_users().count() == 1: + if gallery.gallery.get_linked_users().count() == 1: anonymousGallery = UserGallery() anonymousGallery.user = external anonymousGallery.mode = "w" @@ -250,7 +249,7 @@ def details(request, user_name): olds = [] for old in olds: oldtutos.append(get_info_old_tuto(old)) - return render_template("member/profile.html", { + return render(request, "member/profile.html", { "usr": usr, "profile": profile, "bans": bans, @@ -393,7 +392,7 @@ def tutorials(request): else: user_tutorials = profile.get_tutos() - return render_template("tutorial/member/index.html", + return render(request, "tutorial/member/index.html", {"tutorials": user_tutorials, "type": type}) @@ -421,7 +420,7 @@ def articles(request): else: user_articles = profile.get_articles() - return render_template('article/member/index.html', {'articles': user_articles, 'type': state}) + return render(request, 'article/member/index.html', {'articles': user_articles, 'type': state}) # settings for public profile @@ -457,7 +456,7 @@ def settings_mini_profile(request, user_name): return redirect(reverse("zds.member.views.details", args=[profile.user.username])) else: - return render_template("member/settings/profile.html", c) + return render(request, "member/settings/profile.html", c) else: form = MiniProfileForm(initial={ "biography": profile.biography, @@ -466,7 +465,7 @@ def settings_mini_profile(request, user_name): "sign": profile.sign, }) c = {"form": form, "profile": profile} - return render_template("member/settings/profile.html", c) + return render(request, "member/settings/profile.html", c) @can_write_and_read_now @@ -505,7 +504,7 @@ def settings_profile(request): _(u"Le profil a correctement été mis à jour.")) return redirect(reverse("zds.member.views.settings_profile")) else: - return render_template("member/settings/profile.html", c) + return render(request, "member/settings/profile.html", c) else: form = ProfileForm(initial={ "biography": profile.biography, @@ -518,7 +517,7 @@ def settings_profile(request): "sign": profile.sign, }) c = {"form": form} - return render_template("member/settings/profile.html", c) + return render(request, "member/settings/profile.html", c) @can_write_and_read_now @@ -563,11 +562,11 @@ def settings_account(request): messages.error(request, _(u"Une erreur est survenue.")) return redirect(reverse("zds.member.views.settings_account")) else: - return render_template("member/settings/account.html", c) + return render(request, "member/settings/account.html", c) else: form = ChangePasswordForm(request.user) c = {"form": form} - return render_template("member/settings/account.html", c) + return render(request, "member/settings/account.html", c) @can_write_and_read_now @@ -588,11 +587,11 @@ def settings_user(request): old.save() return redirect(old.profile.get_absolute_url()) else: - return render_template("member/settings/user.html", c) + return render(request, "member/settings/user.html", c) else: form = ChangeUserForm() c = {"form": form} - return render_template("member/settings/user.html", c) + return render(request, "member/settings/user.html", c) def login_view(request): @@ -642,17 +641,19 @@ def login_view(request): else: messages.error(request, _(u"Les identifiants fournis ne sont pas valides.")) - form = LoginForm() - form.helper.form_action = reverse("zds.member.views.login_view") + if next_page is not None: + form = LoginForm() form.helper.form_action += "?next=" + next_page + else: + form = LoginForm() + csrf_tk["error"] = error csrf_tk["form"] = form csrf_tk["next_page"] = next_page - return render_template("member/login.html", + return render(request, "member/login.html", {"form": form, - "csrf_tk": csrf_tk, - "next_page": next_page}) + "csrf_tk": csrf_tk}) @login_required @@ -707,11 +708,11 @@ def register_view(request): msg.send() except: msg = None - return render_template("member/register/success.html", {}) + return render(request, "member/register/success.html", {}) else: - return render_template("member/register/index.html", {"form": form}) + return render(request, "member/register/index.html", {"form": form}) form = RegisterForm() - return render_template("member/register/index.html", {"form": form}) + return render(request, "member/register/index.html", {"form": form}) def forgot_password(request): @@ -745,12 +746,12 @@ def forgot_password(request): [usr.email]) msg.attach_alternative(message_html, "text/html") msg.send() - return render_template("member/forgot_password/success.html") + return render(request, "member/forgot_password/success.html") else: - return render_template("member/forgot_password/index.html", + return render(request, "member/forgot_password/index.html", {"form": form}) form = ForgotPasswordForm() - return render_template("member/forgot_password/index.html", {"form": form}) + return render(request, "member/forgot_password/index.html", {"form": form}) def new_password(request): @@ -769,15 +770,15 @@ def new_password(request): # User can't confirm his request if it is too late. if datetime.now() > token.date_end: - return render_template("member/new_password/failed.html") + return render(request, "member/new_password/failed.html") token.user.set_password(password) token.user.save() token.delete() - return render_template("member/new_password/success.html") + return render(request, "member/new_password/success.html") else: - return render_template("member/new_password.html", {"form": form}) + return render(request, "member/new_password.html", {"form": form}) form = NewPasswordForm(identifier=token.user.username) - return render_template("member/new_password/index.html", {"form": form}) + return render(request, "member/new_password/index.html", {"form": form}) def active_account(request): @@ -793,12 +794,12 @@ def active_account(request): # User can't confirm his request if he is already activated. if usr.is_active: - return render_template("member/register/token_already_used.html") + return render(request, "member/register/token_already_used.html") # User can't confirm his request if it is too late. if datetime.now() > token.date_end: - return render_template("member/register/token_failed.html", + return render(request, "member/register/token_failed.html", {"token": token}) usr.is_active = True usr.save() @@ -807,38 +808,38 @@ def active_account(request): bot = get_object_or_404(User, username=settings.ZDS_APP['member']['bot_account']) msg = _( - u'Bonjour **{0}**,' + u'Bonjour **{username}**,' u'\n\n' u'Ton compte a été activé, et tu es donc officiellement ' - u'membre de la communauté de {4}.' + u'membre de la communauté de {site_name}.' u'\n\n' - u'{4} est une communauté dont le but est de diffuser des ' + u'{site_name} est une communauté dont le but est de diffuser des ' u'connaissances au plus grand nombre.' u'\n\n' - u'Sur ce site, tu trouveras un ensemble de [tutoriels]({1}) dans ' + u'Sur ce site, tu trouveras un ensemble de [tutoriels]({tutorials_url}) dans ' u'plusieurs domaines et plus particulièrement autour de l\'informatique ' - u'et des sciences. Tu y retrouveras aussi des [articles]({2}) ' + u'et des sciences. Tu y retrouveras aussi des [articles]({articles_url}) ' u'traitant de sujets d\'actualité ou non, qui, tout comme les tutoriels, ' - u'sont écrits par des [membres]({3}) de la communauté. ' + u'sont écrits par des [membres]({members_url}) de la communauté. ' u'Pendant tes lectures et ton apprentissage, si jamais tu as des ' - u'questions à poser, tu retrouveras sur les [forums]({4}) des personnes ' + u'questions à poser, tu retrouveras sur les [forums]({forums_url}) des personnes ' u'prêtes à te filer un coup de main et ainsi t\'éviter de passer ' u'plusieurs heures sur un problème.' u'\n\n' u'L\'ensemble du contenu disponible sur le site est et sera toujours gratuit, ' - u'car la communauté de {4} est attachée aux valeurs du libre ' + u'car la communauté de {site_name} est attachée aux valeurs du libre ' u'partage et désire apporter le savoir à tout le monde quels que soient ses moyens.' u'\n\n' u'En espérant que tu te plairas ici, ' u'je te laisse maintenant faire un petit tour.' u'\n\n' u'Clem\'')\ - .format(usr.username, - settings.ZDS_APP['site']['url'] + reverse("zds.tutorial.views.index"), - settings.ZDS_APP['site']['url'] + reverse("zds.article.views.index"), - settings.ZDS_APP['site']['url'] + reverse("zds.member.views.index"), - settings.ZDS_APP['site']['url'] + reverse("zds.forum.views.index"), - settings.ZDS_APP['site']['litteral_name']) + .format(username=usr.username, + tutorials_url=settings.ZDS_APP['site']['url'] + reverse("zds.tutorial.views.index"), + articles_url=settings.ZDS_APP['site']['url'] + reverse("zds.article.views.index"), + members_url=settings.ZDS_APP['site']['url'] + reverse("zds.member.views.index"), + forums_url=settings.ZDS_APP['site']['url'] + reverse("zds.forum.views.index"), + site_name=settings.ZDS_APP['site']['litteral_name']) send_mp( bot, [usr], @@ -851,7 +852,7 @@ def active_account(request): ) token.delete() form = LoginForm(initial={'username': usr.username}) - return render_template("member/register/token_success.html", {"usr": usr, "form": form}) + return render(request, "member/register/token_success.html", {"usr": usr, "form": form}) def generate_token_account(request): @@ -890,7 +891,7 @@ def generate_token_account(request): msg.send() except: msg = None - return render_template('member/register/success.html', {}) + return render(request, 'member/register/success.html', {}) def get_client_ip(request): @@ -1070,7 +1071,7 @@ def settings_promote(request, user_pk): 'groups': user.groups.all(), 'activation': user.is_active }) - return render_template('member/settings/promote.html', { + return render(request, 'member/settings/promote.html', { "usr": user, "profile": profile, "form": form @@ -1085,7 +1086,7 @@ def member_from_ip(request, ip): raise PermissionDenied members = Profile.objects.filter(last_ip_address=ip).order_by('-last_visit') - return render_template('member/settings/memberip.html', { + return render(request, 'member/settings/memberip.html', { "members": members, "ip": ip }) @@ -1108,7 +1109,7 @@ def modify_karma(request): note.comment = request.POST["warning"] try: note.value = int(request.POST["points"]) - except: + except (KeyError, ValueError): note.value = 0 note.save() diff --git a/zds/mp/tests/tests_views.py b/zds/mp/tests/tests_views.py index 0b2295af48..c4e197df0f 100644 --- a/zds/mp/tests/tests_views.py +++ b/zds/mp/tests/tests_views.py @@ -339,6 +339,23 @@ def test_success_get_with_and_without_username(self): self.profile2.user.username, response2.context['form'].initial['participants']) + def test_success_get_with_and_without_title(self): + + response = self.client.get(reverse('zds.mp.views.new')) + + self.assertEqual(200, response.status_code) + self.assertIsNone( + response.context['form'].initial['title']) + + response2 = self.client.get( + reverse('zds.mp.views.new') + + '?title=Test titre') + + self.assertEqual(200, response2.status_code) + self.assertEqual( + 'Test titre', + response2.context['form'].initial['title']) + def test_fail_get_with_username_not_exist(self): response2 = self.client.get( diff --git a/zds/mp/views.py b/zds/mp/views.py index 333f4beda6..79e306e31d 100644 --- a/zds/mp/views.py +++ b/zds/mp/views.py @@ -13,13 +13,13 @@ from django.db import transaction from django.db.models import Q from django.http import Http404 -from django.shortcuts import redirect, get_object_or_404 +from django.shortcuts import redirect, get_object_or_404, render from django.template import Context from django.template.loader import get_template from django.views.decorators.http import require_POST from django.forms.util import ErrorList -from zds.utils import render_template, slugify +from zds.utils import slugify from zds.utils.mps import send_mp from zds.utils.paginator import paginator_range from zds.utils.templatetags.emarkdown import emarkdown @@ -73,7 +73,7 @@ def index(request): shown_privatetopics = paginator.page(paginator.num_pages) page = paginator.num_pages - return render_template('mp/index.html', { + return render(request, 'mp/index.html', { 'privatetopics': shown_privatetopics, 'pages': paginator_range(page, paginator.num_pages), 'nb': page }) @@ -134,7 +134,7 @@ def topic(request, topic_pk, topic_slug): # Build form to add an answer for the current topid. form = PrivatePostForm(g_topic, request.user) - return render_template('mp/topic/index.html', { + return render(request, 'mp/topic/index.html', { 'topic': g_topic, 'posts': res, 'pages': paginator_range(page_nbr, paginator.num_pages), @@ -158,7 +158,7 @@ def new(request): 'subtitle': request.POST['subtitle'], 'text': request.POST['text'], }) - return render_template('mp/topic/new.html', { + return render(request, 'mp/topic/new.html', { 'form': form, }) @@ -186,7 +186,7 @@ def new(request): and list_part[0] == request.user.username): errors = form._errors.setdefault("participants", ErrorList()) errors.append(_(u'Vous êtes déjà auteur du message')) - return render_template('mp/topic/new.html', { + return render(request, 'mp/topic/new.html', { 'form': form, }) @@ -201,7 +201,7 @@ def new(request): return redirect(p_topic.get_absolute_url()) else: - return render_template('mp/topic/new.html', { + return render(request, 'mp/topic/new.html', { 'form': form, }) else: @@ -217,11 +217,16 @@ def new(request): if len(dest_list) > 0: dest = ', '.join(dest_list) + title = None + if 'title' in request.GET: + title = request.GET['title'] + form = PrivateTopicForm(username=request.user.username, initial={ 'participants': dest, + 'title': title }) - return render_template('mp/topic/new.html', { + return render(request, 'mp/topic/new.html', { 'form': form, }) @@ -286,7 +291,7 @@ def answer(request): form = PrivatePostForm(g_topic, request.user, initial={ 'text': data['text'] }) - return render_template('mp/post/new.html', { + return render(request, 'mp/post/new.html', { 'topic': g_topic, 'last_post_pk': last_post_pk, 'posts': posts, @@ -352,7 +357,7 @@ def answer(request): return redirect(post.get_absolute_url()) else: - return render_template('mp/post/new.html', { + return render(request, 'mp/post/new.html', { 'topic': g_topic, 'last_post_pk': last_post_pk, 'newpost': newpost, @@ -380,7 +385,7 @@ def answer(request): form = PrivatePostForm(g_topic, request.user, initial={ 'text': text }) - return render_template('mp/post/new.html', { + return render(request, 'mp/post/new.html', { 'topic': g_topic, 'posts': posts, 'last_post_pk': last_post_pk, @@ -433,7 +438,7 @@ def edit_post(request): form.helper.form_action = reverse( 'zds.mp.views.edit_post') + '?message=' + str(post_pk) - return render_template('mp/post/edit.html', { + return render(request, 'mp/post/edit.html', { 'post': post, 'topic': g_topic, 'form': form, @@ -453,7 +458,7 @@ def edit_post(request): }) form.helper.form_action = reverse( 'zds.mp.views.edit_post') + '?message=' + str(post_pk) - return render_template('mp/post/edit.html', { + return render(request, 'mp/post/edit.html', { 'post': post, 'topic': g_topic, 'text': post.text, @@ -488,10 +493,14 @@ def leave(request): @require_POST @transaction.atomic def add_participant(request): - ptopic = get_object_or_404(PrivateTopic, pk=request.POST['topic_pk']) + try: + ptopic = get_object_or_404(PrivateTopic, pk=request.POST['topic_pk']) + except KeyError: + messages.warning( + request, _(u'La conversation que vous avez essayé d\'utiliser n\'existe pas.')) # check if user is the author of topic - if not ptopic.author == request.user: + if ptopic is not None and not ptopic.author == request.user: raise PermissionDenied try: @@ -509,6 +518,9 @@ def add_participant(request): messages.success( request, _(u'Le membre a bien été ajouté à la conversation.')) + except KeyError: + messages.warning( + request, _(u'Le membre que vous avez essayé d\'ajouter n\'existe pas.')) except: messages.warning( request, _(u'Le membre que vous avez essayé d\'ajouter n\'existe pas.')) diff --git a/zds/pages/views.py b/zds/pages/views.py index a0367f7612..fff7004529 100644 --- a/zds/pages/views.py +++ b/zds/pages/views.py @@ -11,6 +11,7 @@ from django.core.urlresolvers import reverse from django.template import Context from django.template.loader import get_template +from django.shortcuts import render from zds import settings from zds.article.models import get_last_articles @@ -18,7 +19,6 @@ from zds.pages.forms import AssocSubscribeForm from zds.settings import SITE_ROOT from zds.tutorial.models import get_last_tutorials -from zds.utils import render_template from zds.utils.models import Alert from django.utils.translation import ugettext as _ @@ -41,10 +41,10 @@ def home(request): try: with open(os.path.join(SITE_ROOT, 'quotes.txt'), 'r') as fh: quote = random.choice(fh.readlines()) - except: + except IOError: quote = settings.ZDS_APP['site']['slogan'] - return render_template('home.html', { + return render(request, 'home.html', { 'last_tutorials': tutos, 'last_articles': articles, 'quote': quote, @@ -52,12 +52,12 @@ def home(request): def index(request): - return render_template('pages/index.html') + return render(request, 'pages/index.html') def about(request): """Display many informations about the website.""" - return render_template('pages/about.html') + return render(request, 'pages/about.html') @can_write_and_read_now @@ -99,15 +99,15 @@ def assoc_subscribe(request): # reset the form after successfull validation form = AssocSubscribeForm() - return render_template("pages/assoc_subscribe.html", {"form": form}) + return render(request, "pages/assoc_subscribe.html", {"form": form}) form = AssocSubscribeForm(initial={'email': request.user.email}) - return render_template("pages/assoc_subscribe.html", {"form": form}) + return render(request, "pages/assoc_subscribe.html", {"form": form}) def association(request): """Display association's presentation.""" - return render_template('pages/association.html') + return render(request, 'pages/association.html') def contact(request): @@ -118,7 +118,7 @@ def contact(request): devs = User.objects.filter( groups__in=Group.objects.filter( name__contains='dev')).all() - return render_template('pages/contact.html', { + return render(request, 'pages/contact.html', { 'staffs': staffs, 'devs': devs }) @@ -126,12 +126,12 @@ def contact(request): def eula(request): """End-User Licence Agreement.""" - return render_template('pages/eula.html') + return render(request, 'pages/eula.html') def cookies(request): """Cookies explaination page.""" - return render_template('pages/cookies.html') + return render(request, 'pages/cookies.html') @can_write_and_read_now @@ -143,6 +143,6 @@ def alerts(request): alerts = Alert.objects.all().order_by('-pubdate') - return render_template('pages/alerts.html', { + return render(request, 'pages/alerts.html', { 'alerts': alerts, }) diff --git a/zds/search/views.py b/zds/search/views.py index efb28c5693..c5e4befda3 100644 --- a/zds/search/views.py +++ b/zds/search/views.py @@ -1,9 +1,11 @@ # coding: utf-8 +from django.shortcuts import render + from haystack.views import SearchView + from zds.search.constants import MODEL_NAMES from zds.utils.paginator import paginator_range -from zds.utils import render_template class CustomSearchView(SearchView): @@ -28,4 +30,4 @@ def create_response(self): context['suggestion'] = self.form.get_suggestion() context.update(self.extra_context()) - return render_template(self.template, context) + return render(self.request, self.template, context) diff --git a/zds/settings.py b/zds/settings.py index c5d92c25d4..785033ab0b 100644 --- a/zds/settings.py +++ b/zds/settings.py @@ -142,7 +142,12 @@ 'django.core.context_processors.static', 'django.core.context_processors.request', 'django.core.context_processors.tz', - 'django.contrib.messages.context_processors.messages' + 'django.contrib.messages.context_processors.messages', + 'social.apps.django_app.context_processors.backends', + 'social.apps.django_app.context_processors.login_redirect', + # ZDS context processors + 'zds.utils.context_processor.app_settings', + 'zds.utils.context_processor.git_version', ) CRISPY_TEMPLATE_PACK = 'bootstrap' @@ -164,6 +169,7 @@ 'email_obfuscator', 'haystack', 'munin', + 'social.apps.django_app.default', # Apps DB tables are created in THIS order by default # --> Order is CRITICAL to properly handle foreign keys @@ -191,6 +197,8 @@ 'avatar_mini': {'size': (24, 24), 'crop': True}, 'tutorial_illu': {'size': (60, 60), 'crop': True}, 'article_illu': {'size': (60, 60), 'crop': True}, + 'help_illu': {'size': (48, 48), 'crop': True}, + 'help_mini_illu': {'size': (26, 26), 'crop': True}, 'gallery': {'size': (120, 120), 'crop': True}, 'content': {'size': (960, 960), 'crop': False}, }, @@ -251,6 +259,7 @@ SERVE = False PANDOC_LOC = '' +PANDOC_PDF_PARAM = "--latex-engine=xelatex --template=../../assets/tex/template.tex -s -S -N --toc -V documentclass=scrbook -V lang=francais -V mainfont=Merriweather -V monofont=\"Andale Mono\" -V fontsize=12pt -V geometry:margin=1in " # LOG PATH FOR PANDOC LOGGING PANDOC_LOG = './pandoc.log' PANDOC_LOG_STATE = False @@ -308,7 +317,8 @@ 'logo': { 'code': u"CC-BY", 'title': u"Creative Commons License", - 'description': u"Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.", + 'description': u"Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - " + u"Partage dans les Mêmes Conditions 4.0 International.", 'url_image': u"http://i.creativecommons.org/l/by-nc-sa/4.0/80x15.png", 'url_license': u"http://creativecommons.org/licenses/by-nc-sa/4.0/", 'author': u"MaxRoyo" @@ -321,11 +331,13 @@ 'url_license': u"http://creativecommons.org/licenses/by-nc-sa/4.0/" }, 'source': { - 'code' : u"GPL v3", + 'code': u"GPL v3", 'url_license': u"http://www.gnu.org/licenses/gpl-3.0.html", 'provider_name': u"Progdupeupl", 'provider_url': u"http://progdupeu.pl", - } + }, + 'licence_info_title': u'http://zestedesavoir.com/tutoriels/281/le-droit-dauteur-creative-commons-et-les-licences-sur-zeste-de-savoir/', + 'licence_info_link': u'Le droit d\'auteur, Creative Commons et les licences sur Zeste de Savoir' }, 'hosting': { 'name': u"OVH", @@ -343,12 +355,15 @@ 'image_max_size': 1024 * 1024, }, 'article': { - 'repo_path': os.path.join(SITE_ROOT, 'articles-data'), + 'home_number': 5, + 'repo_path': os.path.join(SITE_ROOT, 'articles-data') }, 'tutorial': { 'repo_path': os.path.join(SITE_ROOT, 'tutoriels-private'), 'repo_public_path': os.path.join(SITE_ROOT, 'tutoriels-public'), - 'default_license_pk': 7 + 'default_license_pk': 7, + 'home_number': 5, + 'helps_per_page': 20 }, 'forum': { 'posts_per_page': 21, @@ -359,9 +374,41 @@ 'beta_forum_id': 1, 'max_post_length': 1000000, 'top_tag_max': 5, + }, + 'paginator':{ + 'folding_limit': 4 } } +LOGIN_REDIRECT_URL = "/" + +AUTHENTICATION_BACKENDS = ('social.backends.facebook.FacebookOAuth2', + 'social.backends.google.GoogleOAuth2', + 'social.backends.twitter.TwitterOAuth', + 'django.contrib.auth.backends.ModelBackend') +SOCIAL_AUTH_GOOGLE_OAUTH2_USE_DEPRECATED_API = True + +SOCIAL_AUTH_PIPELINE = ( + 'social.pipeline.social_auth.social_details', + 'social.pipeline.social_auth.social_uid', + 'social.pipeline.social_auth.auth_allowed', + 'social.pipeline.social_auth.social_user', + 'social.pipeline.user.get_username', + 'social.pipeline.user.create_user', + 'zds.member.models.save_profile', + 'social.pipeline.social_auth.associate_user', + 'social.pipeline.social_auth.load_extra_data', + 'social.pipeline.user.user_details' +) + +# redefine for real key and secret code +SOCIAL_AUTH_FACEBOOK_KEY = "" +SOCIAL_AUTH_FACEBOOK_SECRET = "" +SOCIAL_AUTH_TWITTER_KEY = "bVWLd2pDe6F12SXRa5FQyVTze" +SOCIAL_AUTH_TWITTER_SECRET = "pwdQ3trdMdT7Y669aKRwVM6tivrYsx3psbFnRJ5Tq4Wy1VjBNk" +SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = "696570367703-r6hc7mdd27t1sktdkivpnc5b25i0uip2.apps.googleusercontent.com" +SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = "mApWNh3stCsYHwsGuWdbZWP8" + # Load the production settings, overwrite the existing ones if needed try: from settings_prod import * diff --git a/zds/settings_test.py b/zds/settings_test.py index 2e216dd2b7..223d8a975c 100644 --- a/zds/settings_test.py +++ b/zds/settings_test.py @@ -1,10 +1,10 @@ DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'zds_test', - 'USER': 'travis', - 'PASSWORD': '', - 'HOST': '127.0.0.1', - 'PORT': '', - } -} \ No newline at end of file + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'zds_test', + 'USER': 'travis', + 'PASSWORD': '', + 'HOST': '127.0.0.1', + 'PORT': '', + } +} diff --git a/zds/tutorial/factories.py b/zds/tutorial/factories.py index 5a7ac99919..dd9147ee72 100644 --- a/zds/tutorial/factories.py +++ b/zds/tutorial/factories.py @@ -12,20 +12,21 @@ from zds.utils.models import SubCategory, Licence from zds.gallery.factories import GalleryFactory, UserGalleryFactory from zds.utils.tutorials import export_tutorial +from zds.tutorial.views import mep content = ( - u'Ceci est un contenu de tutoriel utile et à tester un peu partout' - u'Ce contenu ira aussi bien dans les introductions, que dans les conclusions et les extraits ' - u'le gros intéret étant qu\'il renferme des images pour tester l\'execution coté pandoc ' - u'Exemple d\'image ![Ma pepite souris](http://blog.science-infuse.fr/public/souris.jpg)' - u'\nExemple d\'image ![Image inexistante](http://blog.science-infuse.fr/public/inv_souris.jpg)' - u'\nExemple de gif ![](http://corigif.free.fr/oiseau/img/oiseau_004.gif)' - u'\nExemple de gif inexistant ![](http://corigif.free.fr/oiseau/img/ironman.gif)' + u'Ceci est un contenu de tutoriel utile et à tester un peu partout\n\n ' + u'Ce contenu ira aussi bien dans les introductions, que dans les conclusions et les extraits \n\n ' + u'le gros intéret étant qu\'il renferme des images pour tester l\'execution coté pandoc \n\n ' + u'Exemple d\'image ![Ma pepite souris](http://blog.science-infuse.fr/public/souris.jpg)\n\n ' + u'\nExemple d\'image ![Image inexistante](http://blog.science-infuse.fr/public/inv_souris.jpg)\n\n ' + u'\nExemple de gif ![](http://corigif.free.fr/oiseau/img/oiseau_004.gif)\n\n ' + u'\nExemple de gif inexistant ![](http://corigif.free.fr/oiseau/img/ironman.gif)\n\n ' u'Une image de type wikipedia qui fait tomber des tests ![](https://s.qwant.com/thumbr/?u=http%3A%2' - u'F%2Fwww.blogoergosum.com%2Fwp-content%2Fuploads%2F2010%2F02%2Fwikipedia-logo.jpg&h=338&w=600)' - u'Image dont le serveur n\'existe pas ![](http://unknown.image.zds)' - u'\n Attention les tests ne doivent pas crasher ' - u'qu\'un sujet abandonné !') + u'F%2Fwww.blogoergosum.com%2Fwp-content%2Fuploads%2F2010%2F02%2Fwikipedia-logo.jpg&h=338&w=600)\n\n ' + u'Image dont le serveur n\'existe pas ![](http://unknown.image.zds)\n\n ' + u'\n Attention les tests ne doivent pas crasher \n\n \n\n \n\n ' + u'qu\'un sujet abandonné !\n\n ') content_light = u'Un contenu light pour quand ce n\'est pas vraiment ça qui est testé' @@ -326,7 +327,7 @@ class SubCategoryFactory(factory.DjangoModelFactory): slug = factory.Sequence(lambda n: 'sous-categorie-{0}'.format(n)) -class VaidationFactory(factory.DjangoModelFactory): +class ValidationFactory(factory.DjangoModelFactory): FACTORY_FOR = Validation @@ -340,3 +341,18 @@ class LicenceFactory(factory.DjangoModelFactory): def _prepare(cls, create, **kwargs): licence = super(LicenceFactory, cls)._prepare(create, **kwargs) return licence + + +class PublishedMiniTutorial(MiniTutorialFactory): + FACTORY_FOR = Tutorial + + @classmethod + def _prepare(cls, create, **kwargs): + tutorial = super(PublishedMiniTutorial, cls)._prepare(create, **kwargs) + tutorial.pubdate = datetime.now() + tutorial.sha_public = tutorial.sha_draft + tutorial.source = '' + tutorial.sha_validation = None + mep(tutorial, tutorial.sha_draft) + tutorial.save() + return tutorial diff --git a/zds/tutorial/forms.py b/zds/tutorial/forms.py index ab6c84ebd4..e2001098e8 100644 --- a/zds/tutorial/forms.py +++ b/zds/tutorial/forms.py @@ -4,14 +4,13 @@ from crispy_forms.bootstrap import StrictButton from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Fieldset, Submit, Field, \ +from crispy_forms.layout import HTML, Layout, Fieldset, Submit, Field, \ ButtonHolder, Hidden from django.core.urlresolvers import reverse -from zds.tutorial.models import TYPE_CHOICES from zds.utils.forms import CommonLayoutModalText, CommonLayoutEditor, CommonLayoutVersionEditor from zds.utils.models import SubCategory, Licence -from zds.tutorial.models import Tutorial +from zds.tutorial.models import Tutorial, TYPE_CHOICES, HelpWriting from django.utils.translation import ugettext_lazy as _ @@ -92,7 +91,14 @@ class TutorialForm(FormWithTitle): ) licence = forms.ModelChoiceField( - label=_(u"Licence de votre publication"), + label=( + _(u'Licence de votre publication (En savoir plus sur les licences et {2})') + .format( + settings.ZDS_APP['site']['licenses']['licence_info_title'], + settings.ZDS_APP['site']['licenses']['licence_info_link'], + settings.ZDS_APP['site']['name'] + ) + ), queryset=Licence.objects.all(), required=True, empty_label=None @@ -109,6 +115,13 @@ class TutorialForm(FormWithTitle): ) ) + helps = forms.ModelMultipleChoiceField( + label=_(u"Pour m'aider je cherche un..."), + queryset=HelpWriting.objects.all(), + required=False, + widget=forms.SelectMultiple() + ) + def __init__(self, *args, **kwargs): super(TutorialForm, self).__init__(*args, **kwargs) self.helper = FormHelper() @@ -123,8 +136,15 @@ def __init__(self, *args, **kwargs): Field('introduction', css_class='md-editor'), Field('conclusion', css_class='md-editor'), Hidden('last_hash', '{{ last_hash }}'), - Field('subcategory'), Field('licence'), + Field('subcategory'), + 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 tutoriel apparaitra alors sur la page d'aide.

    ")), + Field('helps'), Field('msg_commit'), ButtonHolder( StrictButton('Valider', type='submit'), @@ -582,3 +602,26 @@ def __init__(self, *args, **kwargs): type='submit'),), Hidden('tutorial', '{{ tutorial.pk }}'), Hidden('version', '{{ version }}'), ) + + +class ActivJsForm(forms.Form): + + js_support = forms.BooleanField( + label='Cocher pour activer JSFiddle', + required=False, + initial=True + ) + + def __init__(self, *args, **kwargs): + super(ActivJsForm, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.form_action = reverse('zds.tutorial.views.activ_js') + self.helper.form_method = 'post' + + self.helper.layout = Layout( + Field('js_support'), + ButtonHolder( + StrictButton( + _(u'Valider'), + type='submit'),), + Hidden('tutorial', '{{ tutorial.pk }}'), ) diff --git a/zds/tutorial/migrations/0005_auto.py b/zds/tutorial/migrations/0005_auto.py new file mode 100644 index 0000000000..ad0ae067ba --- /dev/null +++ b/zds/tutorial/migrations/0005_auto.py @@ -0,0 +1,207 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding M2M table for field helps on 'Tutorial' + m2m_table_name = db.shorten_name(u'tutorial_tutorial_helps') + db.create_table(m2m_table_name, ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('tutorial', models.ForeignKey(orm[u'tutorial.tutorial'], null=False)), + ('helpwriting', models.ForeignKey(orm[u'utils.helpwriting'], null=False)) + )) + db.create_unique(m2m_table_name, ['tutorial_id', 'helpwriting_id']) + + + def backwards(self, orm): + # Removing M2M table for field helps on 'Tutorial' + db.delete_table(db.shorten_name(u'tutorial_tutorial_helps')) + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'gallery.gallery': { + 'Meta': {'object_name': 'Gallery'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subtitle': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'gallery.image': { + 'Meta': {'object_name': 'Image'}, + 'gallery': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gallery.Gallery']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'legend': ('django.db.models.fields.CharField', [], {'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'physical': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'tutorial.chapter': { + 'Meta': {'object_name': 'Chapter'}, + 'conclusion': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gallery.Image']", 'null': 'True', 'blank': 'True'}), + 'introduction': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'part': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Part']", 'null': 'True', 'blank': 'True'}), + 'position_in_part': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'position_in_tutorial': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']", 'null': 'True', 'blank': 'True'}) + }, + u'tutorial.extract': { + 'Meta': {'object_name': 'Extract'}, + 'chapter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Chapter']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'position_in_chapter': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'tutorial.note': { + 'Meta': {'object_name': 'Note', '_ormbases': [u'utils.Comment']}, + u'comment_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['utils.Comment']", 'unique': 'True', 'primary_key': 'True'}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']"}) + }, + u'tutorial.part': { + 'Meta': {'object_name': 'Part'}, + 'conclusion': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'introduction': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'position_in_tutorial': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']"}) + }, + u'tutorial.tutorial': { + 'Meta': {'object_name': 'Tutorial'}, + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}), + 'conclusion': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'create_at': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'gallery': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gallery.Gallery']", 'null': 'True', 'blank': 'True'}), + 'helps': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['utils.HelpWriting']", 'db_index': 'True', 'symmetrical': 'False'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gallery.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'images': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'introduction': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'is_locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_note': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_note'", 'null': 'True', 'to': u"orm['tutorial.Note']"}), + 'licence': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Licence']", 'null': 'True', 'blank': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'sha_beta': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_draft': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_public': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_validation': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'source': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'subcategory': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['utils.SubCategory']", 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '10', 'db_index': 'True'}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'tutorial.tutorialread': { + 'Meta': {'object_name': 'TutorialRead'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Note']"}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tuto_notes_read'", 'to': u"orm['auth.User']"}) + }, + u'tutorial.validation': { + 'Meta': {'object_name': 'Validation'}, + 'comment_authors': ('django.db.models.fields.TextField', [], {}), + 'comment_validator': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_proposition': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'date_reserve': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'date_validation': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '10'}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']", 'null': 'True', 'blank': 'True'}), + 'validator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'author_validations'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'version': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}) + }, + u'utils.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': u"orm['auth.User']"}), + 'dislike': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments-editor'", 'null': 'True', 'to': u"orm['auth.User']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.CharField', [], {'max_length': '39'}), + 'is_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'like': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'position': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'text_hidden': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80'}), + 'text_html': ('django.db.models.fields.TextField', [], {}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'utils.helpwriting': { + 'Meta': {'object_name': 'HelpWriting'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '20'}), + 'tablelabel': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '20'}) + }, + u'utils.licence': { + 'Meta': {'object_name': 'Licence'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'description': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.subcategory': { + 'Meta': {'object_name': 'SubCategory'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subtitle': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + } + } + + complete_apps = ['tutorial'] \ No newline at end of file diff --git a/zds/tutorial/migrations/0005_auto__add_field_tutorial_js_support.py b/zds/tutorial/migrations/0005_auto__add_field_tutorial_js_support.py new file mode 100644 index 0000000000..20a5216f02 --- /dev/null +++ b/zds/tutorial/migrations/0005_auto__add_field_tutorial_js_support.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Tutorial.js_support' + db.add_column(u'tutorial_tutorial', 'js_support', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Tutorial.js_support' + db.delete_column(u'tutorial_tutorial', 'js_support') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'gallery.gallery': { + 'Meta': {'object_name': 'Gallery'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subtitle': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'gallery.image': { + 'Meta': {'object_name': 'Image'}, + 'gallery': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gallery.Gallery']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'legend': ('django.db.models.fields.CharField', [], {'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'physical': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'tutorial.chapter': { + 'Meta': {'object_name': 'Chapter'}, + 'conclusion': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gallery.Image']", 'null': 'True', 'blank': 'True'}), + 'introduction': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'part': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Part']", 'null': 'True', 'blank': 'True'}), + 'position_in_part': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'position_in_tutorial': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']", 'null': 'True', 'blank': 'True'}) + }, + u'tutorial.extract': { + 'Meta': {'object_name': 'Extract'}, + 'chapter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Chapter']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'position_in_chapter': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'tutorial.note': { + 'Meta': {'object_name': 'Note', '_ormbases': [u'utils.Comment']}, + u'comment_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['utils.Comment']", 'unique': 'True', 'primary_key': 'True'}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']"}) + }, + u'tutorial.part': { + 'Meta': {'object_name': 'Part'}, + 'conclusion': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'introduction': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'position_in_tutorial': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']"}) + }, + u'tutorial.tutorial': { + 'Meta': {'object_name': 'Tutorial'}, + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}), + 'conclusion': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'create_at': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'gallery': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gallery.Gallery']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gallery.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'images': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'introduction': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'is_locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'js_support': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_note': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_note'", 'null': 'True', 'to': u"orm['tutorial.Note']"}), + 'licence': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Licence']", 'null': 'True', 'blank': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'sha_beta': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_draft': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_public': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_validation': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'source': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'subcategory': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['utils.SubCategory']", 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '10', 'db_index': 'True'}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'tutorial.tutorialread': { + 'Meta': {'object_name': 'TutorialRead'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Note']"}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tuto_notes_read'", 'to': u"orm['auth.User']"}) + }, + u'tutorial.validation': { + 'Meta': {'object_name': 'Validation'}, + 'comment_authors': ('django.db.models.fields.TextField', [], {}), + 'comment_validator': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_proposition': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'date_reserve': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'date_validation': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '10'}), + 'tutorial': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tutorial.Tutorial']", 'null': 'True', 'blank': 'True'}), + 'validator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'author_validations'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'version': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}) + }, + u'utils.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': u"orm['auth.User']"}), + 'dislike': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments-editor'", 'null': 'True', 'to': u"orm['auth.User']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.CharField', [], {'max_length': '39'}), + 'is_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'like': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'position': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'text_hidden': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80'}), + 'text_html': ('django.db.models.fields.TextField', [], {}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'utils.licence': { + 'Meta': {'object_name': 'Licence'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'description': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.subcategory': { + 'Meta': {'object_name': 'SubCategory'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subtitle': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + } + } + + complete_apps = ['tutorial'] \ No newline at end of file diff --git a/zds/tutorial/models.py b/zds/tutorial/models.py index 1695860d58..0da04e1a8d 100644 --- a/zds/tutorial/models.py +++ b/zds/tutorial/models.py @@ -4,10 +4,10 @@ import shutil try: import ujson as json_reader -except: +except ImportError: try: import simplejson as json_reader - except: + except ImportError: import json as json_reader import json as json_writer @@ -16,13 +16,14 @@ from django.conf import settings from django.contrib.auth.models import User from django.core.urlresolvers import reverse +from django.utils.http import urlencode from django.db import models from datetime import datetime from git.repo import Repo from zds.gallery.models import Image, Gallery from zds.utils import slugify, get_current_user -from zds.utils.models import SubCategory, Licence, Comment +from zds.utils.models import SubCategory, Licence, Comment, HelpWriting from zds.utils.tutorials import get_blob, export_tutorial @@ -109,6 +110,9 @@ class Meta: related_name='last_note', verbose_name='Derniere note') is_locked = models.BooleanField('Est verrouillé', default=False) + js_support = models.BooleanField('Support du Javascript', default=False) + + helps = models.ManyToManyField(HelpWriting, verbose_name='Aides', db_index=True) def __unicode__(self): return self.title @@ -134,6 +138,15 @@ def get_absolute_url_beta(self): else: return self.get_absolute_url() + def get_absolute_contact_url(self): + """ Get url to send a new mp for collaboration """ + get = '?'+urlencode({'title': u'Collaboration - {}'.format(self.title)}) + + for author in self.authors.all(): + get += '&'+urlencode({'username': author.username}) + + return reverse('zds.mp.views.new')+get + def get_edit_url(self): return reverse('zds.tutorial.views.modify_tutorial') + \ '?tutorial={0}'.format(self.pk) @@ -248,7 +261,8 @@ def load_json_for_public(self, sha=None): repo = Repo(self.get_path()) mantuto = get_blob(repo.commit(sha).tree, 'manifest.json') data = json_reader.loads(mantuto) - + if 'licence' in data: + data['licence'] = Licence.objects.filter(code=data['licence']).first() return data def load_json(self, path=None, online=False): @@ -265,7 +279,8 @@ def load_json(self, path=None, online=False): json_data = open(man_path) data = json_reader.load(json_data) json_data.close() - + if 'licence' in data: + data['licence'] = Licence.objects.filter(code=data['licence']).first() return data def dump_json(self, path=None): @@ -449,10 +464,11 @@ def have_epub(self): def get_last_tutorials(): + n = settings.ZDS_APP['tutorial']['home_number'] tutorials = Tutorial.objects.all()\ .exclude(sha_public__isnull=True)\ .exclude(sha_public__exact='')\ - .order_by('-pubdate')[:5] + .order_by('-pubdate')[:n] return tutorials diff --git a/zds/tutorial/tests/tests.py b/zds/tutorial/tests/tests.py index 8def91bef7..50869b7156 100644 --- a/zds/tutorial/tests/tests.py +++ b/zds/tutorial/tests/tests.py @@ -29,7 +29,7 @@ from zds.gallery.factories import GalleryFactory from zds.tutorial.models import Note, Tutorial, Validation, Extract, Part, Chapter from zds.tutorial.views import insert_into_zip -from zds.utils.models import SubCategory, Licence, Alert +from zds.utils.models import SubCategory, Licence, Alert, HelpWriting from zds.utils.misc import compute_hash @@ -295,6 +295,38 @@ def test_import_archive(self): shutil.rmtree(temp) os.remove(zip_path) + def test_fail_import_archive(self): + + login_check = self.client.login( + username=self.user_author.username, + password='hostel77') + self.assertEqual(login_check, True) + + temp = os.path.join(tempfile.gettempdir(), "temp") + if not os.path.exists(temp): + os.makedirs(temp, mode=0777) + + # test fail import + with open(os.path.join(temp, 'test.py'), 'a') as f: + f.write('something') + + result = self.client.post( + reverse('zds.tutorial.views.import_tuto'), + { + 'file': open( + os.path.join( + temp, + 'test.py'), + 'r'), + 'tutorial': self.bigtuto.pk, + 'import-archive': "importer"}, + follow=False + ) + self.assertEqual(result.status_code, 200) + + # delete temporary data directory + shutil.rmtree(temp) + def test_add_note(self): """To test add note for tutorial.""" user1 = ProfileFactory().user @@ -2221,7 +2253,7 @@ def test_workflow_licence(self): # test change in JSON : json = tuto.load_json() - self.assertEquals(json['licence'], new_licence.code) + self.assertEquals(json['licence'].code, new_licence.code) # then logout ... self.client.logout() @@ -2255,7 +2287,7 @@ def test_workflow_licence(self): # test change in JSON : json = tuto.load_json() - self.assertEquals(json['licence'], self.licence.code) + self.assertEquals(json['licence'].code, self.licence.code) # then logout ... self.client.logout() @@ -2312,7 +2344,7 @@ def test_workflow_licence(self): # test change in JSON (normaly, nothing has) : json = tuto.load_json() - self.assertEquals(json['licence'], self.licence.code) + self.assertEquals(json['licence'].code, self.licence.code) def test_workflow_archive_tuto(self): """ensure the behavior of archive with a big tutorial""" @@ -2645,7 +2677,39 @@ def test_import_archive(self): shutil.rmtree(temp) os.remove(zip_path) - def add_test_extract_named_introduction(self): + def test_fail_import_archive(self): + + login_check = self.client.login( + username=self.user_author.username, + password='hostel77') + self.assertEqual(login_check, True) + + temp = os.path.join(tempfile.gettempdir(), "temp") + if not os.path.exists(temp): + os.makedirs(temp, mode=0777) + + # test fail import + with open(os.path.join(temp, 'test.py'), 'a') as f: + f.write('something') + + result = self.client.post( + reverse('zds.tutorial.views.import_tuto'), + { + 'file': open( + os.path.join( + temp, + 'test.py'), + 'r'), + 'tutorial': self.minituto.pk, + 'import-archive': "importer"}, + follow=False + ) + self.assertEqual(result.status_code, 200) + + # delete temporary data directory + shutil.rmtree(temp) + + def test_add_extract_named_introduction(self): """test the use of an extract named introduction""" self.client.login(username=self.user_author, @@ -2664,13 +2728,13 @@ def add_test_extract_named_introduction(self): tuto = Tutorial.objects.get(pk=self.minituto.pk) self.assertEqual(Extract.objects.all().count(), 1) intro_path = os.path.join(tuto.get_path(), "introduction.md") - extract_path = Extract.objects.get(pk=1).get_path() + extract_path = Extract.objects.first().get_path() self.assertNotEqual(intro_path, extract_path) self.assertTrue(os.path.isfile(intro_path)) self.assertTrue(os.path.isfile(extract_path)) - def add_test_extract_named_conclusion(self): - """test the use of an extract named introduction""" + def test_add_extract_named_conclusion(self): + """test the use of an extract named conclusion""" self.client.login(username=self.user_author, password='hostel77') @@ -2688,7 +2752,7 @@ def add_test_extract_named_conclusion(self): tuto = Tutorial.objects.get(pk=self.minituto.pk) self.assertEqual(Extract.objects.all().count(), 1) ccl_path = os.path.join(tuto.get_path(), "conclusion.md") - extract_path = Extract.objects.get(pk=1).get_path() + extract_path = Extract.objects.first().get_path() self.assertNotEqual(ccl_path, extract_path) self.assertTrue(os.path.isfile(ccl_path)) self.assertTrue(os.path.isfile(extract_path)) @@ -3703,7 +3767,7 @@ def test_workflow_licence(self): # test change in JSON : json = tuto.load_json() - self.assertEquals(json['licence'], new_licence.code) + self.assertEquals(json['licence'].code, new_licence.code) # then logout ... self.client.logout() @@ -3737,7 +3801,7 @@ def test_workflow_licence(self): # test change in JSON : json = tuto.load_json() - self.assertEquals(json['licence'], self.licence.code) + self.assertEquals(json['licence'].code, self.licence.code) # then logout ... self.client.logout() @@ -3794,7 +3858,7 @@ def test_workflow_licence(self): # test change in JSON (normaly, nothing has) : json = tuto.load_json() - self.assertEquals(json['licence'], self.licence.code) + self.assertEquals(json['licence'].code, self.licence.code) def test_workflow_archive_tuto(self): """ensure the behavior of archive with a mini tutorial""" @@ -3956,6 +4020,80 @@ def test_workflow_archive_tuto(self): os.remove(draft_zip_path) os.remove(online_zip_path) + def test_help_to_perfect_tuto(self): + """ This test aim to unit test the "help me to write my tutorial" + interface. It is testing if the back-end is always sending back + good datas """ + + helps = HelpWriting.objects.all() + + # currently the tutorial is published with no beta, so back-end should return 0 tutorial + response = self.client.post( + reverse('zds.tutorial.views.help_tutorial'), + follow=False + ) + self.assertEqual(200, response.status_code) + tutos = response.context['tutorials'] + self.assertEqual(len(tutos), 0) + + # then active the beta on tutorial : + ForumFactory( + category=CategoryFactory(position=1), + position_in_category=1) + # first, login with author : + self.assertEqual( + self.client.login( + username=self.user_author.username, + password='hostel77'), + True) + sha_draft = Tutorial.objects.get(pk=self.minituto.pk).sha_draft + response = self.client.post( + reverse('zds.tutorial.views.modify_tutorial'), + { + 'tutorial': self.minituto.pk, + 'activ_beta': True, + 'version': sha_draft + }, + follow=False + ) + self.assertEqual(302, response.status_code) + sha_beta = Tutorial.objects.get(pk=self.minituto.pk).sha_beta + self.assertEqual(sha_draft, sha_beta) + response = self.client.post( + reverse('zds.tutorial.views.help_tutorial'), + follow=False + ) + self.assertEqual(200, response.status_code) + tutos = response.context['tutorials'] + self.assertEqual(len(tutos), 1) + + # However if we ask with a filter we will still get 0 + for helping in helps: + response = self.client.post( + reverse('zds.tutorial.views.help_tutorial') + + u'?type={}'.format(helping.slug), + follow=False + ) + self.assertEqual(200, response.status_code) + tutos = response.context['tutorials'] + self.assertEqual(len(tutos), 0) + + # now tutorial is positive for every options + # if we ask for any help we should get a positive answer for every filter + for helping in helps: + self.minituto.helps.add(helping) + self.minituto.save() + + for helping in helps: + response = self.client.post( + reverse('zds.tutorial.views.help_tutorial') + + u'?type={}'.format(helping.slug), + follow=False + ) + self.assertEqual(200, response.status_code) + tutos = response.context['tutorials'] + self.assertEqual(len(tutos), 1) + def tearDown(self): if os.path.isdir(settings.ZDS_APP['tutorial']['repo_path']): shutil.rmtree(settings.ZDS_APP['tutorial']['repo_path']) diff --git a/zds/tutorial/urls.py b/zds/tutorial/urls.py index c1b6736880..15e3f3e8d3 100644 --- a/zds/tutorial/urls.py +++ b/zds/tutorial/urls.py @@ -102,7 +102,8 @@ 'zds.tutorial.views.invalid_tutorial'), url(r'^validation/historique/(?P\d+)/$', 'zds.tutorial.views.history_validation'), - + url(r'^activation_js/$', + 'zds.tutorial.views.activ_js'), # Reactions url(r'^message/editer/$', 'zds.tutorial.views.edit_note'), @@ -114,4 +115,9 @@ # Moderation url(r'^resolution_alerte/$', 'zds.tutorial.views.solve_alert'), + + # Help + url(r'^aides/$', + 'zds.tutorial.views.help_tutorial'), ) + diff --git a/zds/tutorial/views.py b/zds/tutorial/views.py index 58f4a14680..4226392951 100644 --- a/zds/tutorial/views.py +++ b/zds/tutorial/views.py @@ -8,12 +8,12 @@ from urlparse import urlparse, parse_qs try: import ujson as json_reader -except: +except ImportError: try: import simplejson as json_reader - except: + except ImportError: import json as json_reader - +import json import json as json_writer import shutil import re @@ -32,24 +32,23 @@ from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage from django.core.urlresolvers import reverse from django.db import transaction -from django.db.models import Q +from django.db.models import Q, Count from django.http import Http404, HttpResponse -from django.shortcuts import get_object_or_404, redirect +from django.shortcuts import get_object_or_404, redirect, render from django.utils.encoding import smart_str from django.views.decorators.http import require_POST from git import Repo, Actor from lxml import etree from forms import TutorialForm, PartForm, ChapterForm, EmbdedChapterForm, \ - ExtractForm, ImportForm, ImportArchiveForm, NoteForm, AskValidationForm, ValidForm, RejectForm + ExtractForm, ImportForm, ImportArchiveForm, NoteForm, AskValidationForm, ValidForm, RejectForm, ActivJsForm from models import Tutorial, Part, Chapter, Extract, Validation, never_read, \ - mark_read, Note + mark_read, Note, HelpWriting from zds.gallery.models import Gallery, UserGallery, Image from zds.member.decorator import can_write_and_read_now from zds.member.models import get_info_old_tuto, Profile from zds.member.views import get_client_ip from zds.forum.models import Forum, Topic -from zds.utils import render_template from zds.utils import slugify from zds.utils.models import Alert from zds.utils.models import Category, Licence, CommentLike, CommentDislike, \ @@ -103,7 +102,7 @@ def index(request): mandata = tutorial.load_json_for_public() tutorial.load_dic(mandata) tuto_versions.append(mandata) - return render_template("tutorial/index.html", {"tutorials": tuto_versions, "tag": tag}) + return render(request, "tutorial/index.html", {"tutorials": tuto_versions, "tag": tag}) # Staff actions. @@ -169,7 +168,7 @@ def list_validation(request): )).filter(tutorial__subcategory__in=[subcategory]) \ .order_by("date_proposition")\ .all() - return render_template("tutorial/validation/index.html", + return render(request, "tutorial/validation/index.html", {"validations": validations}) @@ -214,7 +213,7 @@ def diff(request, tutorial_pk, tutorial_slug): repo = Repo(tutorial.get_path()) hcommit = repo.commit(sha) tdiff = hcommit.diff("HEAD~1") - return render_template("tutorial/tutorial/diff.html", { + return render(request, "tutorial/tutorial/diff.html", { "tutorial": tutorial, "path_add": tdiff.iter_change_type("A"), "path_ren": tdiff.iter_change_type("R"), @@ -235,7 +234,7 @@ def history(request, tutorial_pk, tutorial_slug): repo = Repo(tutorial.get_path()) logs = repo.head.reference.log() logs = sorted(logs, key=attrgetter("time"), reverse=True) - return render_template("tutorial/tutorial/history.html", + return render(request, "tutorial/tutorial/history.html", {"tutorial": tutorial, "logs": logs}) @@ -262,7 +261,7 @@ def history_validation(request, tutorial_pk): tutorial__subcategory__in=[subcategory]) \ .order_by("date_proposition" ).all() - return render_template("tutorial/validation/history.html", + return render(request, "tutorial/validation/history.html", {"validations": validations, "tutorial": tutorial}) @@ -879,6 +878,8 @@ def view_tutorial(request, tutorial_pk, tutorial_slug): validation = Validation.objects.filter(tutorial__pk=tutorial.pk)\ .order_by("-date_proposition")\ .first() + form_js = ActivJsForm(initial={"js_support": tutorial.js_support}) + if tutorial.source: form_ask_validation = AskValidationForm(initial={"source": tutorial.source}) form_valid = ValidForm(initial={"source": tutorial.source}) @@ -886,15 +887,22 @@ def view_tutorial(request, tutorial_pk, tutorial_slug): form_ask_validation = AskValidationForm() form_valid = ValidForm() form_reject = RejectForm() - return render_template("tutorial/tutorial/view.html", { + + if tutorial.js_support: + is_js = "js" + else: + is_js = "" + return render(request, "tutorial/tutorial/view.html", { "tutorial": mandata, "chapter": chapter, "parts": parts, "version": sha, "validation": validation, "formAskValidation": form_ask_validation, + "formJs": form_js, "formValid": form_valid, "formReject": form_reject, + "is_js": is_js }) @@ -978,11 +986,12 @@ def view_tutorial_online(request, tutorial_pk, tutorial_slug): mandata['get_parts'] = parts # If the user is authenticated - if request.user.is_authenticated(): + # We check if he can post a tutorial or not with + # antispam filter. + mandata['antispam'] = tutorial.antispam() # If the user is never read, we mark this tutorial read. - if never_read(tutorial): mark_read(tutorial) @@ -1028,7 +1037,7 @@ def view_tutorial_online(request, tutorial_pk, tutorial_slug): # Build form to send a note for the current tutorial. form = NoteForm(tutorial, request.user) - return render_template("tutorial/tutorial/view_online.html", { + return render(request, "tutorial/tutorial/view_online.html", { "tutorial": mandata, "chapter": chapter, "parts": parts, @@ -1107,6 +1116,10 @@ def add_tutorial(request): for subcat in form.cleaned_data["subcategory"]: tutorial.subcategory.add(subcat) + # Add helps if needed + for helpwriting in form.cleaned_data["helps"]: + tutorial.helps.add(helpwriting) + # We need to save the tutorial before changing its author list # since it's a many-to-many relationship @@ -1135,7 +1148,7 @@ def add_tutorial(request): 'licence': Licence.objects.get(pk=settings.ZDS_APP['tutorial']['default_license_pk']) } ) - return render_template("tutorial/tutorial/new.html", {"form": form}) + return render(request, "tutorial/tutorial/new.html", {"form": form}) @can_write_and_read_now @@ -1172,9 +1185,9 @@ def edit_tutorial(request): "subcategory": tutorial.subcategory.all(), "introduction": tutorial.get_introduction(), "conclusion": tutorial.get_conclusion(), - + "helps": tutorial.helps.all(), }) - return render_template("tutorial/tutorial/edit.html", + return render(request, "tutorial/tutorial/edit.html", { "tutorial": tutorial, "form": form, "last_hash": compute_hash([introduction, conclusion]), @@ -1228,15 +1241,21 @@ def edit_tutorial(request): action="maj", msg=request.POST.get('msg_commit', None) ) + tutorial.subcategory.clear() for subcat in form.cleaned_data["subcategory"]: tutorial.subcategory.add(subcat) + + tutorial.helps.clear() + for help in form.cleaned_data["helps"]: + tutorial.helps.add(help) + tutorial.save() return redirect(tutorial.get_absolute_url()) else: json = tutorial.load_json() if "licence" in json: - licence = Licence.objects.filter(code=json["licence"]).all()[0] + licence = json['licence'] else: licence = Licence.objects.get( pk=settings.ZDS_APP['tutorial']['default_license_pk'] @@ -1249,8 +1268,9 @@ def edit_tutorial(request): "subcategory": tutorial.subcategory.all(), "introduction": tutorial.get_introduction(), "conclusion": tutorial.get_conclusion(), + "helps": tutorial.helps.all(), }) - return render_template("tutorial/tutorial/edit.html", + return render(request, "tutorial/tutorial/edit.html", {"tutorial": tutorial, "form": form, "last_hash": compute_hash([introduction, conclusion])}) # Parts. @@ -1324,10 +1344,16 @@ def view_part( if not find: raise Http404 - return render_template("tutorial/part/view.html", + if tutorial.js_support: + is_js = "js" + else: + is_js = "" + + return render(request, "tutorial/part/view.html", {"tutorial": mandata, "part": final_part, - "version": sha}) + "version": sha, + "is_js": is_js}) def view_part_online( @@ -1393,7 +1419,7 @@ def view_part_online( if not find: raise Http404 - return render_template("tutorial/part/view_online.html", {"part": final_part}) + return render(request, "tutorial/part/view_online.html", {"part": final_part}) @can_write_and_read_now @@ -1451,7 +1477,7 @@ def add_part(request): return redirect(part.get_absolute_url()) else: form = PartForm() - return render_template("tutorial/part/new.html", {"tutorial": tutorial, + return render(request, "tutorial/part/new.html", {"tutorial": tutorial, "form": form}) @@ -1545,7 +1571,7 @@ def edit_part(request): form = PartForm({"title": part.title, "introduction": part.get_introduction(), "conclusion": part.get_conclusion()}) - return render_template("tutorial/part/edit.html", + return render(request, "tutorial/part/edit.html", { "part": part, "last_hash": compute_hash([introduction, conclusion]), @@ -1583,7 +1609,7 @@ def edit_part(request): form = PartForm({"title": part.title, "introduction": part.get_introduction(), "conclusion": part.get_conclusion()}) - return render_template("tutorial/part/edit.html", + return render(request, "tutorial/part/edit.html", { "part": part, "last_hash": compute_hash([introduction, conclusion]), @@ -1684,12 +1710,18 @@ def view_chapter( next_chapter = (chapter_tab[final_position + 1] if final_position + 1 < len(chapter_tab) else None) - return render_template("tutorial/chapter/view.html", { + if tutorial.js_support: + is_js = "js" + else: + is_js = "" + + return render(request, "tutorial/chapter/view.html", { "tutorial": mandata, "chapter": final_chapter, "prev": prev_chapter, "next": next_chapter, "version": sha, + "is_js": is_js }) @@ -1789,7 +1821,7 @@ def view_chapter_online( prev_chapter = (chapter_tab[final_position - 1] if final_position > 0 else None) next_chapter = (chapter_tab[final_position + 1] if final_position + 1 < len(chapter_tab) else None) - return render_template("tutorial/chapter/view_online.html", { + return render(request, "tutorial/chapter/view_online.html", { "chapter": final_chapter, "parts": parts, "prev": prev_chapter, @@ -1875,7 +1907,7 @@ def add_chapter(request): else: form = ChapterForm() - return render_template("tutorial/chapter/new.html", {"part": part, + return render(request, "tutorial/chapter/new.html", {"part": part, "form": form}) @@ -1999,7 +2031,7 @@ def edit_chapter(request): # avoid collision if content_has_changed([introduction, conclusion], data["last_hash"]): form = render_chapter_form(chapter) - return render_template("tutorial/part/edit.html", + return render(request, "tutorial/part/edit.html", { "chapter": chapter, "last_hash": compute_hash([introduction, conclusion]), @@ -2047,7 +2079,7 @@ def edit_chapter(request): return redirect(chapter.get_absolute_url()) else: form = render_chapter_form(chapter) - return render_template("tutorial/chapter/edit.html", {"chapter": chapter, + return render(request, "tutorial/chapter/edit.html", {"chapter": chapter, "last_hash": compute_hash([introduction, conclusion]), "form": form}) @@ -2084,8 +2116,9 @@ def add_extract(request): if "preview" in data: form = ExtractForm(initial={"title": data["title"], - "text": data["text"]}) - return render_template("tutorial/extract/new.html", + "text": data["text"], + 'msg_commit': data['msg_commit']}) + return render(request, "tutorial/extract/new.html", {"chapter": chapter, "form": form}) else: @@ -2109,7 +2142,7 @@ def add_extract(request): else: form = ExtractForm() - return render_template("tutorial/extract/new.html", {"chapter": chapter, + return render(request, "tutorial/extract/new.html", {"chapter": chapter, "form": form}) @@ -2144,9 +2177,10 @@ def edit_extract(request): if "preview" in data: form = ExtractForm(initial={ "title": data["title"], - "text": data["text"] + "text": data["text"], + 'msg_commit': data['msg_commit'] }) - return render_template("tutorial/extract/edit.html", + return render(request, "tutorial/extract/edit.html", { "extract": extract, "form": form, "last_hash": compute_hash([extract.get_path()]) @@ -2155,8 +2189,9 @@ def edit_extract(request): if content_has_changed([extract.get_path()], data["last_hash"]): form = ExtractForm(initial={ "title": extract.title, - "text": extract.get_text()}) - return render_template("tutorial/extract/edit.html", + "text": extract.get_text(), + 'msg_commit': data['msg_commit']}) + return render(request, "tutorial/extract/edit.html", { "extract": extract, "last_hash": compute_hash([extract.get_path()]), @@ -2189,7 +2224,7 @@ def edit_extract(request): else: form = ExtractForm({"title": extract.title, "text": extract.get_text()}) - return render_template("tutorial/extract/edit.html", + return render(request, "tutorial/extract/edit.html", { "extract": extract, "last_hash": compute_hash([extract.get_path()]), @@ -2285,7 +2320,7 @@ def find_tuto(request, pk_user): tutorial.load_dic(mandata, sha=tutorial.sha_beta) tuto_versions.append(mandata) - return render_template("tutorial/member/beta.html", + return render(request, "tutorial/member/beta.html", {"tutorials": tuto_versions, "usr": display_user}) else: tutorials = Tutorial.objects.all().filter( @@ -2298,7 +2333,7 @@ def find_tuto(request, pk_user): tutorial.load_dic(mandata) tuto_versions.append(mandata) - return render_template("tutorial/member/online.html", {"tutorials": tuto_versions, + return render(request, "tutorial/member/online.html", {"tutorials": tuto_versions, "usr": display_user}) @@ -2329,7 +2364,7 @@ def upload_images(images, tutorial): except IOError: try: os.makedirs(ph_temp) - except: + except OSError: pass zfile.close() return mapping @@ -2605,6 +2640,7 @@ def import_tuto(request): if form_archive.is_valid(): (check, reason) = import_archive(request) if not check: + form = ImportForm() messages.error(request, reason) else: messages.success(request, reason) @@ -2624,10 +2660,11 @@ def import_tuto(request): olds = [] for old in olds: oldtutos.append(get_info_old_tuto(old)) - return render_template( - "tutorial/tutorial/import.html", {"form": form, - "form_archive": form_archive, - "old_tutos": oldtutos}) + return render( + request, + "tutorial/tutorial/import.html", + {"form": form, "form_archive": form_archive, "old_tutos": oldtutos} + ) # Handling repo @@ -3117,8 +3154,9 @@ def mep(tutorial, sha): if os.path.isdir(del_path): try: shutil.rmtree(del_path) - except: + except OSError: shutil.rmtree(u"\\\\?\{0}".format(del_path)) + # WARNING: this can throw another OSError shutil.copytree(tutorial.get_path(), prod_path) repo.head.reset(commit=sha, index=True, working_tree=True) @@ -3168,8 +3206,12 @@ def mep(tutorial, sha): target = u"\\\\?\{0}".format(target) html_file = open(target, "w") + if tutorial.js_support: + is_js = "js" + else: + is_js = "" if md_file_contenu is not None: - html_file.write(emarkdown(md_file_contenu)) + html_file.write(emarkdown(md_file_contenu, is_js)) html_file.close() # load markdown out @@ -3193,12 +3235,7 @@ def mep(tutorial, sha): + os.path.join(prod_path, tutorial.slug) + ".md -o " + os.path.join(prod_path, tutorial.slug) + ".html" + pandoc_debug_str) - os.system(settings.PANDOC_LOC + "pandoc " + "--latex-engine=xelatex " - + "--template=../../assets/tex/template.tex " + "-s " + "-S " - + "-N " + "--toc " + "-V documentclass=scrbook " - + "-V lang=francais " + "-V mainfont=Merriweather " - + "-V monofont=\"Andale Mono\" " + "-V fontsize=12pt " - + "-V geometry:margin=1in " + os.system(settings.PANDOC_LOC + "pandoc " + settings.PANDOC_PDF_PARAM + " " + os.path.join(prod_path, tutorial.slug) + ".md " + "-o " + os.path.join(prod_path, tutorial.slug) + ".pdf" + pandoc_debug_str) @@ -3217,8 +3254,9 @@ def un_mep(tutorial): if os.path.isdir(del_path): try: shutil.rmtree(del_path) - except: + except OSError: shutil.rmtree(u"\\\\?\{0}".format(del_path)) + # WARNING: this can throw another OSError @can_write_and_read_now @@ -3272,13 +3310,17 @@ def answer(request): if "preview" in data or newnote: form = NoteForm(tutorial, request.user, initial={"text": data["text"]}) - return render_template("tutorial/comment/new.html", { - "tutorial": tutorial, - "last_note_pk": last_note_pk, - "newnote": newnote, - "notes": notes, - "form": form, - }) + if request.is_ajax(): + return HttpResponse(json.dumps({"text": emarkdown(data["text"])}), + content_type='application/json') + else: + return render(request, "tutorial/comment/new.html", { + "tutorial": tutorial, + "last_note_pk": last_note_pk, + "newnote": newnote, + "notes": notes, + "form": form, + }) else: # Saving the message @@ -3299,7 +3341,7 @@ def answer(request): tutorial.save() return redirect(note.get_absolute_url()) else: - return render_template("tutorial/comment/new.html", { + return render(request, "tutorial/comment/new.html", { "tutorial": tutorial, "last_note_pk": last_note_pk, "newnote": newnote, @@ -3329,7 +3371,7 @@ def answer(request): note_cite.get_absolute_url()) form = NoteForm(tutorial, request.user, initial={"text": text}) - return render_template("tutorial/comment/new.html", { + return render(request, "tutorial/comment/new.html", { "tutorial": tutorial, "notes": notes, "last_note_pk": last_note_pk, @@ -3378,6 +3420,21 @@ def solve_alert(request): return redirect(note.get_absolute_url()) +@login_required +@require_POST +def activ_js(request): + + # only for staff + + if not request.user.has_perm("tutorial.change_tutorial"): + raise PermissionDenied + tutorial = get_object_or_404(Tutorial, pk=request.POST["tutorial"]) + tutorial.js_support = "js_support" in request.POST + tutorial.save() + + return redirect(tutorial.get_absolute_url()) + + @can_write_and_read_now @login_required def edit_note(request): @@ -3435,8 +3492,13 @@ def edit_note(request): initial={"text": request.POST["text"]}) form.helper.form_action = reverse("zds.tutorial.views.edit_note") \ + "?message=" + str(note_pk) - return render_template( - "tutorial/comment/edit.html", {"note": note, "tutorial": g_tutorial, "form": form}) + if request.is_ajax(): + return HttpResponse(json.dumps({"text": emarkdown(request.POST["text"])}), + content_type='application/json') + else: + return render(request, + "tutorial/comment/edit.html", + {"note": note, "tutorial": g_tutorial, "form": form}) if "delete_message" not in request.POST and "signal_message" \ not in request.POST and "show_message" not in request.POST: @@ -3453,8 +3515,7 @@ def edit_note(request): form = NoteForm(g_tutorial, request.user, initial={"text": note.text}) form.helper.form_action = reverse("zds.tutorial.views.edit_note") \ + "?message=" + str(note_pk) - return render_template( - "tutorial/comment/edit.html", {"note": note, "tutorial": g_tutorial, "form": form}) + return render(request, "tutorial/comment/edit.html", {"note": note, "tutorial": g_tutorial, "form": form}) @can_write_and_read_now @@ -3542,3 +3603,42 @@ def dislike_note(request): return HttpResponse(json_writer.dumps(resp)) else: return redirect(note.get_absolute_url()) + + +def help_tutorial(request): + """fetch all tutorials that needs help""" + + # Retrieve type of the help. Default value is any help + type = request.GET.get('type', None) + + if type is not None: + aide = get_object_or_404(HelpWriting, slug=type) + tutos = Tutorial.objects.filter(helps=aide) \ + .all() + else: + tutos = Tutorial.objects.annotate(total=Count('helps'), shasize=Count('sha_beta')) \ + .filter((Q(sha_beta__isnull=False) & Q(shasize__gt=0)) | Q(total__gt=0)) \ + .all() + + # Paginator + paginator = Paginator(tutos, settings.ZDS_APP['tutorial']['helps_per_page']) + page = request.GET.get('page') + + try: + shown_tutos = paginator.page(page) + page = int(page) + except PageNotAnInteger: + shown_tutos = paginator.page(1) + page = 1 + except EmptyPage: + shown_tutos = paginator.page(paginator.num_pages) + page = paginator.num_pages + + aides = HelpWriting.objects.all() + + return render(request, "tutorial/tutorial/help.html", { + "tutorials": shown_tutos, + "helps": aides, + "pages": paginator_range(page, paginator.num_pages), + "nb": page + }) diff --git a/zds/urls.py b/zds/urls.py index fb4ce51012..aa2fff8fa2 100644 --- a/zds/urls.py +++ b/zds/urls.py @@ -55,12 +55,14 @@ def location(self, article): priority=0.7 ), 'forums': GenericSitemap( - {'queryset': Forum.objects.filter(group__isnull=True)}, + {'queryset': Forum.objects.filter(group__isnull=True).exclude(pk=settings.ZDS_APP['forum']['beta_forum_id'])}, changefreq='yearly', priority=0.7 ), 'topics': GenericSitemap( - {'queryset': Topic.objects.filter(is_locked=False, forum__group__isnull=True), 'date_field': 'pubdate'}, + {'queryset': Topic.objects.filter(is_locked=False, + forum__group__isnull=True).exclude(forum__pk=settings.ZDS_APP['forum']['beta_forum_id']), + 'date_field': 'pubdate'}, changefreq='hourly', priority=0.7 ), @@ -81,6 +83,8 @@ def location(self, article): url(r'^galerie/', include('zds.gallery.urls')), url(r'^rechercher/', include('zds.search.urls')), url(r'^munin/', include('zds.munin.urls')), + url('', include('social.apps.django_app.urls', namespace='social')), + url('', include('django.contrib.auth.urls', namespace='auth')), ('^munin/', include('munin.urls')), url(r'^$', 'zds.pages.views.home'), diff --git a/zds/utils/__init__.py b/zds/utils/__init__.py index 1fdabf69a3..7030723be6 100644 --- a/zds/utils/__init__.py +++ b/zds/utils/__init__.py @@ -1,10 +1,6 @@ # coding: utf-8 -from django.template import RequestContext, defaultfilters - -from django.shortcuts import render_to_response -from git import Repo -from django.conf import settings +from django.template import defaultfilters try: @@ -23,17 +19,6 @@ def get_current_request(): return getattr(_thread_locals, 'request', None) -def get_git_version(): - try: - repo = Repo(settings.SITE_ROOT) - branch = repo.active_branch - commit = repo.head.commit.hexsha - v = u"{0}/{1}".format(branch, commit[:7]) - return {'name': v, 'url': u'{}/tree/{}'.format(settings.ZDS_APP['site']['repository'], commit)} - except: - return {'name': '', 'url': ''} - - class ThreadLocals(object): def process_request(self, request): @@ -41,15 +26,6 @@ def process_request(self, request): _thread_locals.request = request -def render_template(tmpl, dct=None): - if dct is None: - dct = {} - dct['git_version'] = get_git_version() - dct['app'] = settings.ZDS_APP - return render_to_response( - tmpl, dct, context_instance=RequestContext(get_current_request())) - - def slugify(text): if defaultfilters.slugify(text).strip('') == '': return '--' diff --git a/zds/utils/admin.py b/zds/utils/admin.py index 1cf7c77f9e..4e597fe950 100644 --- a/zds/utils/admin.py +++ b/zds/utils/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin -from zds.utils.models import Alert, Licence, Category, SubCategory, CategorySubCategory, Tag +from zds.utils.models import Alert, Licence, Category, SubCategory, \ + CategorySubCategory, Tag, HelpWriting admin.site.register(Alert) @@ -9,3 +10,4 @@ admin.site.register(Category) admin.site.register(SubCategory) admin.site.register(CategorySubCategory) +admin.site.register(HelpWriting) diff --git a/zds/utils/context_processor.py b/zds/utils/context_processor.py new file mode 100644 index 0000000000..cf860396c4 --- /dev/null +++ b/zds/utils/context_processor.py @@ -0,0 +1,33 @@ +# coding: utf-8 + +from django.conf import settings + +from git import Repo + + +def get_git_version(): + """ + Get the git version of the site. + """ + try: + repo = Repo(settings.SITE_ROOT) + branch = repo.active_branch + commit = repo.head.commit.hexsha + v = u'{0}/{1}'.format(branch, commit[:7]) + return {'name': v, 'url': u'{}/tree/{}'.format(settings.ZDS_APP['site']['repository'], commit)} + except (KeyError, TypeError): + return {'name': '', 'url': ''} + + +def git_version(request): + """ + A context processor to include the git version on all pages. + """ + return {'git_version': get_git_version()} + + +def app_settings(request): + """ + A context processor with all APP settings. + """ + return {'app': settings.ZDS_APP} diff --git a/zds/utils/factories.py b/zds/utils/factories.py new file mode 100644 index 0000000000..a9ca50ea93 --- /dev/null +++ b/zds/utils/factories.py @@ -0,0 +1,39 @@ +# coding: utf-8 +from zds.utils.models import HelpWriting +from zds.utils import slugify +from zds.settings import SITE_ROOT, MEDIA_ROOT +from shutil import copyfile +from os.path import basename, join + +import factory + + +class HelpWritingFactory(factory.DjangoModelFactory): + FACTORY_FOR = HelpWriting + + title = factory.Sequence(lambda n: u"titre de l\'image {0}".format(n)) + slug = factory.LazyAttribute(lambda o: "{0}".format(slugify(o.title))) + tablelabel = factory.LazyAttribute(lambda n: u"Besoin de " + n.title) + + @classmethod + def _prepare(cls, create, **kwargs): + a = super(HelpWritingFactory, cls)._prepare(create, **kwargs) + image_path = kwargs.pop('image_path', None) + fixture_image_path = kwargs.pop('fixture_image_path', None) + + if fixture_image_path is not None: + image_path = join(SITE_ROOT, "fixtures", fixture_image_path) + + if image_path is not None: + copyfile(image_path, join(MEDIA_ROOT, basename(image_path))) + a.image = basename(image_path) + a.save() + + return a + + @classmethod + def _create(cls, target_class, *args, **kwargs): + kwargs.pop('image_path', None) + kwargs.pop('fixture_image_path', None) + + return super(HelpWritingFactory, cls)._create(target_class, *args, **kwargs) diff --git a/zds/utils/forms.py b/zds/utils/forms.py index 67828f0311..5cc92b7c88 100644 --- a/zds/utils/forms.py +++ b/zds/utils/forms.py @@ -1,7 +1,7 @@ # coding: utf-8 from crispy_forms.bootstrap import StrictButton -from crispy_forms.layout import Layout, ButtonHolder, Field, Div +from crispy_forms.layout import Layout, ButtonHolder, Field, Div, HTML from django.utils.translation import ugettext_lazy as _ @@ -11,20 +11,20 @@ def __init__(self, *args, **kwargs): super( CommonLayoutEditor, self).__init__( - Div( - Field('text', css_class='md-editor'), - ButtonHolder( - StrictButton( - _(u'Envoyer'), - type='submit', - name='answer'), - StrictButton( - _(u'Aperçu'), - type='submit', - name='preview', - css_class='btn-grey'), - ), - ), + Field('text', css_class='md-editor'), + HTML("
    "), + HTML("
    "), + StrictButton( + _(u'Envoyer'), + type='submit', + name='answer'), + StrictButton( + _(u'Aperçu'), + type='submit', + name='preview', + css_class='btn-grey'), + HTML("
    "), + HTML("
    "), ) diff --git a/zds/utils/management/__init__.py b/zds/utils/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zds/utils/management/commands/__init__.py b/zds/utils/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zds/utils/management/commands/load_factory_data.py b/zds/utils/management/commands/load_factory_data.py new file mode 100644 index 0000000000..2657036c75 --- /dev/null +++ b/zds/utils/management/commands/load_factory_data.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +import glob +import os +import yaml + +from django.core.management.base import BaseCommand +from django.db import transaction +from zds.settings import MEDIA_ROOT + + +@transaction.atomic +class Command(BaseCommand): + args = 'files' + help = 'Load complex fixtures for ZdS' + + # python manage.py load_factory_data + + def handle(self, *args, **options): + # create "media" folder if not existing + if not os.path.exists(MEDIA_ROOT): + os.mkdir(MEDIA_ROOT) + + for filename in glob.glob(" ".join(args)): + stream = open(filename, 'r') + fixture_list = yaml.load(stream) + for fixture in fixture_list: + splitted = str(fixture["factory"]).split(".") + module_part = ".".join(splitted[:-1]) + m = __import__(module_part) + for comp in splitted[1:-1]: + m = getattr(m, comp) + + obj = getattr(m, splitted[-1])(**fixture["fields"]) + print(obj) diff --git a/zds/utils/management/commands/load_fixtures.py b/zds/utils/management/commands/load_fixtures.py new file mode 100644 index 0000000000..d7d5aeafb2 --- /dev/null +++ b/zds/utils/management/commands/load_fixtures.py @@ -0,0 +1,701 @@ +# coding: utf-8 + +import random +import sys +import time + +from datetime import datetime +from django.core.management.base import BaseCommand +from random import randint +from faker import Factory +from zds.utils.templatetags.emarkdown import emarkdown + +from zds.forum.factories import CategoryFactory, ForumFactory, TopicFactory, PostFactory +from zds.tutorial.factories import BigTutorialFactory, PartFactory, ChapterFactory, NoteFactory, MiniTutorialFactory,\ + ExtractFactory +from zds.article.factories import ReactionFactory, ArticleFactory +from zds.gallery.factories import GalleryFactory, UserGalleryFactory, ImageFactory +from zds.member.factories import StaffProfileFactory, ProfileFactory +from django.contrib.auth.models import User, Permission +from zds.member.models import Profile +from zds.article.models import Article, Validation as AValidation +from zds.tutorial.models import Tutorial, Validation as TValidation +from zds.tutorial.views import mep as mep_tuto +from zds.article.views import mep as mep_art +from zds.forum.models import Forum, Topic, Category as FCategory +from zds.utils.models import Tag, Category as TCategory, CategorySubCategory, SubCategory, Licence +from zds.utils import slugify +from zds import settings +from django.db import transaction + + +def load_member(cli, size, fake, root): + """ + Load members + """ + nb_users = size * 10 + cli.stdout.write(u"Nombres de membres à créer : {}".format(nb_users)) + tps1 = time.time() + cpt = 1 + # member in settings + users_set = ["admin", + settings.ZDS_APP["member"]["external_account"], + settings.ZDS_APP["member"]["anonymous_account"]] + for u in users_set: + us = Profile.objects.filter(user__username=u).first() + if us is None: + profile = ProfileFactory(user__username=u) + profile.user.set_password(u) + profile.user.first_name = u + profile.user.email = fake.free_email() + if u == "admin": + profile.user.is_superuser = True + profile.user.is_staff = True + profile.user.save() + profile.site = fake.url() + profile.biography = fake.text(max_nb_chars=200) + profile.last_ip_address = fake.ipv4() + profile.save() + + for i in range(0, nb_users): + while Profile.objects.filter(user__username="{}{}".format(root, cpt)).count() > 0: + cpt += 1 + profile = ProfileFactory(user__username="{}{}".format(root, cpt)) + profile.user.set_password(profile.user.username) + profile.user.first_name = fake.first_name() + profile.user.last_name = fake.last_name() + profile.user.email = fake.free_email() + profile.user.save() + profile.site = fake.url() + profile.biography = fake.text(max_nb_chars=200) + profile.last_ip_address = fake.ipv4() + profile.save() + cpt += 1 + sys.stdout.write(" User {}/{} \r".format(i+1, nb_users)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_staff(cli, size, fake, root): + """ + Load staff + """ + nb_staffs = size * 3 + cli.stdout.write(u"Nombres de staffs à créer : {}".format(nb_staffs)) + tps1 = time.time() + cpt = 1 + for i in range(0, nb_staffs): + while Profile.objects.filter(user__username="{}staff{}".format(root, cpt)).count() > 0: + cpt += 1 + profile = StaffProfileFactory(user__username="{}staff{}".format(root, cpt)) + profile.user.first_name = fake.first_name() + profile.user.last_name = fake.last_name() + profile.user.email = fake.free_email() + profile.user.save() + profile.site = fake.url() + profile.biography = fake.paragraph() + profile.sign = fake.text(max_nb_chars=80) + profile.last_ip_address = fake.ipv6() + profile.save() + cpt += 1 + sys.stdout.write(" Staff {}/{} \r".format(i+1, nb_staffs)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_gallery(cli, size, fake): + """ + Load galleries + """ + nb_galleries = size * 3 + nb_images = size * 5 + cli.stdout.write(u"Nombres de galéries à créer par utilisateur: {}".format(nb_galleries)) + cli.stdout.write(u"Nombres d'images à créer par gallerie: {}".format(nb_images)) + tps1 = time.time() + nb_users = User.objects.count() + if nb_users == 0: + cli.stdout.write(u"Il n'y a aucun membre actuellement. " + u"Vous devez rajouter les membres dans vos fixtures (member)") + else: + profiles = list(Profile.objects.all()) + for i in range(0, nb_users): + for j in range(0, nb_galleries): + gal = GalleryFactory(title=fake.text(max_nb_chars=80), subtitle=fake.text(max_nb_chars=200)) + UserGalleryFactory(user=profiles[i].user, gallery=gal) + for k in range(0, nb_images): + ImageFactory(gallery=gal) + sys.stdout.write(" User {}/{} \tGallery {}/{} \tImage {}/{} \r". + format(i+1, nb_users, + j+1, nb_galleries, + k+1, nb_images)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_categories_forum(cli, size, fake): + """ + Load categories + """ + nb_categories = size * 2 + cli.stdout.write(u"Nombres de catégories de forum à créer : {}".format(nb_categories)) + tps1 = time.time() + for i in range(0, nb_categories): + cat = CategoryFactory(position=i + 1) + cat.title = fake.word() + cat.save() + sys.stdout.write(" Cat. {}/{} \r".format(i+1, nb_categories)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_forums(cli, size, fake): + """ + Load forums + """ + nb_forums = size * 8 + cli.stdout.write(u"Nombres de Forums à créer : {}".format(nb_forums)) + tps1 = time.time() + nb_categories = FCategory.objects.count() + if nb_categories == 0: + cli.stdout.write(u"Il n'y a aucune catgorie actuellement. " + u"Vous devez rajouter les categories de forum dans vos fixtures (category_forum)") + else: + categories = list(FCategory.objects.all()) + for i in range(0, nb_forums): + forum = ForumFactory(category=categories[i % nb_categories], + position_in_category=(i / nb_categories) + 1) + forum.title = fake.word() + forum.subtitle = fake.sentence(nb_words=15, variable_nb_words=True) + forum.save() + sys.stdout.write(" Forum {}/{} \r".format(i+1, nb_forums)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_tags(cli, size, fake): + """ + Load tags + """ + nb_tags = size * 50 + cli.stdout.write(u"Nombres de Tags de forum à créer : {}".format(nb_tags)) + tps1 = time.time() + for i in range(0, nb_tags): + title = fake.word() + t = Tag(title=title, slug=slugify(title)) + t.save() + sys.stdout.write(" Tag {}/{} \r".format(i+1, nb_tags)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_topics(cli, size, fake): + """ + Load topics + """ + nb_topics = size * 20 + cli.stdout.write(u"Nombres de Topics à créer : {}".format(nb_topics)) + tps1 = time.time() + nb_forums = Forum.objects.count() + if nb_forums == 0: + cli.stdout.write(u"Il n'y a aucun forum actuellement. " + u"Vous devez rajouter les forums dans vos fixtures (forum)") + else: + forums = list(Forum.objects.all()) + nb_users = User.objects.count() + if nb_users == 0: + cli.stdout.write(u"Il n'y a aucun membre actuellement. " + u"Vous devez rajouter les membres dans vos fixtures (member)") + else: + profiles = list(Profile.objects.all()) + nb_tags = Tag.objects.count() + if nb_tags == 0: + cli.stdout.write(u"Il n'y a aucun tag actuellement. " + u"Vous devez rajouter les tags dans vos fixtures (tag)") + else: + for i in range(0, nb_topics): + topic = TopicFactory(forum=forums[i % nb_forums], author=profiles[i % nb_users].user) + if i % 5 == 0: + topic.is_solved = True + if i % 10 == 0: + topic.is_locked = True + if i % 15 == 0: + topic.is_sticky = True + nb_rand_tags = random.randint(0, 5) + for k in range(0, nb_rand_tags): + topic.tags.add(random.randint(1, nb_tags)) + topic.title = fake.text(max_nb_chars=80) + topic.subtitle = fake.text(max_nb_chars=200) + topic.save() + sys.stdout.write(" Topic {}/{} \r".format(i+1, nb_topics)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_posts(cli, size, fake): + """ + Load posts + """ + nb_avg_posts_in_topic = size * 10 + cli.stdout.write(u"Nombres de messages à poster en moyenne dans un sujet : {}".format(nb_avg_posts_in_topic)) + tps1 = time.time() + nb_topics = Topic.objects.count() + if nb_topics == 0: + cli.stdout.write(u"Il n'y a aucun topic actuellement. " + u"Vous devez rajouter les topics dans vos fixtures (topic)") + else: + topics = list(Topic.objects.all()) + nb_users = User.objects.count() + if nb_users == 0: + cli.stdout.write(u"Il n'y a aucun membre actuellement. " + u"Vous devez rajouter les membres dans vos fixtures (member)") + else: + profiles = list(Profile.objects.all()) + for i in range(0, nb_topics): + nb = randint(0, nb_avg_posts_in_topic * 2) + for j in range(0, nb): + if j == 0: + post = PostFactory(topic=topics[i], author=topics[i].author, position=1) + else: + post = PostFactory(topic=topics[i], author=profiles[j % nb_users].user, position=j+1) + post.text = fake.paragraph(nb_sentences=5, variable_nb_sentences=True) + post.text_html = emarkdown(post.text) + if int(nb * 0.3) > 0: + if j % int(nb * 0.3) == 0: + post.is_useful = True + post.save() + sys.stdout.write(" Topic {}/{} \tPost {}/{} \r". + format(i+1, nb_topics, + j+1, nb)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_categories_content(cli, size, fake): + """ + Load categories and subcategories for tutorial and article + """ + + lics = ["CB-BY", "CC-BY-ND", "CC-BY-ND-SA", "CC-BY-SA", "CC", "CC-BY-IO", "Tout-Droits"] + for lic in lics: + ex = Licence.objects.filter(code=lic) + if ex is None: + l = Licence(code=lic, title=lic, description="") + l.save() + categories = [] + sub_categories = [] + nb_categories = size * 5 + nb_sub_categories = size * 10 + cli.stdout.write(u"Nombres de catégories de contenus à créer : {}".format(nb_categories)) + cli.stdout.write(u"Nombres de sous-catégories de contenus à créer : {}".format(nb_sub_categories)) + tps1 = time.time() + for i in range(0, nb_categories): + ttl = fake.word() + cat = TCategory(title=ttl, + description=fake.sentence(nb_words=15, variable_nb_words=True), + slug=slugify(ttl)) + cat.save() + categories.append(cat) + sys.stdout.write(" Cat. {}/{} \r".format(i+1, nb_categories)) + sys.stdout.flush() + + for i in range(0, nb_sub_categories): + ttl = fake.word() + cat = SubCategory(title=ttl, + subtitle=fake.sentence(nb_words=5, variable_nb_words=True), + slug=slugify(ttl)) + cat.save() + sub_categories.append(cat) + sys.stdout.write(" SubCat. {}/{} \r".format(i+1, nb_sub_categories)) + sys.stdout.flush() + + for i in range(0, nb_sub_categories): + h = CategorySubCategory(category=categories[i % nb_categories], subcategory=sub_categories[i], is_main=True) + h.save() + sys.stdout.write(" CatSubCat. {}/{} \r".format(i+1, nb_sub_categories)) + sys.stdout.flush() + + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_comment_article(cli, size, fake): + """ + Load article's comments + """ + nb_avg_posts = size * 20 + cli.stdout.write(u"Nombres de messages à poster en moyenne dans un article : {}".format(nb_avg_posts)) + tps1 = time.time() + nb_articles = Article.objects.filter(sha_public__isnull=False).count() + articles = list(Article.objects.filter(sha_public__isnull=False).all()) + nb_users = User.objects.count() + profiles = list(Profile.objects.all()) + for i in range(0, nb_articles): + nb = randint(0, nb_avg_posts * 2) + for j in range(0, nb): + post = ReactionFactory(article=articles[i], author=profiles[j % nb_users].user, position=j+1) + post.text = fake.paragraph(nb_sentences=5, variable_nb_sentences=True) + post.text_html = emarkdown(post.text) + post.save() + sys.stdout.write(" Article {}/{} \tComment {}/{} \r". + format(i+1, nb_articles, + j+1, nb)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_comment_tutorial(cli, size, fake): + """ + Load tutorial's comments + """ + nb_avg_posts = size * 20 + cli.stdout.write(u"Nombres de messages à poster en moyenne dans un tutoriel : {}".format(nb_avg_posts)) + tps1 = time.time() + nb_tutorials = Tutorial.objects.filter(sha_public__isnull=False).count() + tutorials = list(Tutorial.objects.filter(sha_public__isnull=False).all()) + nb_users = User.objects.count() + profiles = list(Profile.objects.all()) + for i in range(0, nb_tutorials): + nb = randint(0, nb_avg_posts * 2) + for j in range(0, nb): + post = NoteFactory(tutorial=tutorials[i], author=profiles[j % nb_users].user, position=j+1) + post.text = fake.paragraph(nb_sentences=5, variable_nb_sentences=True) + post.text_html = emarkdown(post.text) + post.save() + sys.stdout.write(" Tuto {}/{} \tComment {}/{} \r". + format(i+1, nb_tutorials, + j+1, nb)) + sys.stdout.flush() + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_tutorials(cli, size, fake): + """ + Load tutorials + """ + + tutorials = [] + parts = [] + chapters = [] + + nb_tutos = size * 10 + percent_tutos_validation_in_validation = 0.4 + percent_tutos_validation_with_validator = 0.2 + percent_tutos_public = 0.3 + nb_avg_parts_in_tuto = size * 1 + nb_avg_chapters_in_tuto = size * 1 + nb_avg_extracts_in_tuto = size * 1 + cli.stdout.write(u"Nombres de big tutoriels à créer : {}".format(nb_tutos)) + cli.stdout.write(u"Nombres de big tutoriels en validations : {}" + .format(str(int(nb_tutos * percent_tutos_validation_in_validation)))) + cli.stdout.write(u"Nombres de big tutoriels réservé en validations : {}" + .format(str(int(nb_tutos * percent_tutos_validation_with_validator)))) + cli.stdout.write(u"Nombres de big tutoriels publiés : {}" + .format(str(int(nb_tutos * percent_tutos_public)))) + cli.stdout.write(u"Nombres de mini tutoriels à créer : {}".format(nb_tutos)) + cli.stdout.write(u"Nombres de mini tutoriels en validations : {}" + .format(str(int(nb_tutos * percent_tutos_validation_in_validation)))) + cli.stdout.write(u"Nombres de mini tutoriels réservé en validations : {}" + .format(str(int(nb_tutos * percent_tutos_validation_with_validator)))) + cli.stdout.write(u"Nombres de mini tutoriels publiés : {}" + .format(str(int(nb_tutos * percent_tutos_public)))) + tps1 = time.time() + nb_users = User.objects.count() + if nb_users == 0: + cli.stdout.write(u"Il n'y a aucun membre actuellement." + u"Vous devez rajouter les membre dans vos fixtures (member)") + else: + profiles = list(Profile.objects.all()) + nb_sub_categories = SubCategory.objects.count() + if nb_sub_categories == 0: + cli.stdout.write(u"Il n'y a aucune catégories actuellement." + u"Vous devez rajouter les membre dans vos fixtures (category_content)") + else: + sub_categories = list(SubCategory.objects.all()) + perms = list(Permission.objects.filter(codename__startswith='change_').all()) + staffs = list(User.objects.filter(groups__permissions__in=perms).all()) + nb_staffs = len(staffs) + if nb_staffs == 0: + cli.stdout.write(u"Il n'y a aucun staff actuellement." + u"Vous devez rajouter les staffs dans vos fixtures (staff)") + else: + # big tutorials + for i in range(0, nb_tutos): + tuto = BigTutorialFactory(title=fake.text(max_nb_chars=200), + description=fake.sentence(nb_words=15, variable_nb_words=True)) + tuto.authors.add(profiles[i % nb_users].user) + tuto.subcategory.add(sub_categories[random.randint(0, nb_sub_categories-1)]) + tutorials.append(tuto) + nb_part = randint(0, nb_avg_parts_in_tuto * 2) + for j in range(0, nb_part): + parts.append(PartFactory(tutorial=tutorials[i], + position_in_tutorial=j, + title=fake.text(max_nb_chars=200))) + nb_chap = randint(0, nb_avg_chapters_in_tuto * 2) + for k in range(0, nb_chap): + chapters.append(ChapterFactory(part=parts[j], + position_in_part=k, + position_in_tutorial=j * k, + title=fake.text(max_nb_chars=200))) + nb_ext = randint(0, nb_avg_extracts_in_tuto * 2) + for l in range(0, nb_ext): + ExtractFactory(chapter=chapters[k], + position_in_chapter=l, + title=fake.text(max_nb_chars=200)) + if i < int(nb_tutos * percent_tutos_validation_with_validator): + validator = staffs[random.randint(0, nb_staffs-1)] + v = TValidation(tutorial=tuto, + version=tuto.sha_draft, + date_proposition=datetime.now(), + date_reserve=datetime.now(), + validator=validator, + status="PENDING_V") + v.save() + tuto.sha_validation = tuto.sha_draft + tuto.save() + elif i < int(nb_tutos * (percent_tutos_validation_in_validation + + percent_tutos_validation_with_validator)): + v = TValidation(tutorial=tuto, + version=tuto.sha_draft, + date_proposition=datetime.now()) + v.save() + tuto.sha_validation = tuto.sha_draft + tuto.save() + elif i < int(nb_tutos * (percent_tutos_validation_in_validation + + percent_tutos_validation_with_validator + + percent_tutos_public)): + mep_tuto(tuto, tuto.sha_draft) + v = TValidation(tutorial=tuto, + version=tuto.sha_draft, + date_proposition=datetime.now(), + date_reserve=datetime.now(), + validator=validator, + status="ACCEPT", + comment_validator=fake.text(max_nb_chars=200), + date_validation=datetime.now()) + v.save() + tuto.sha_public = tuto.sha_draft + tuto.save() + sys.stdout.write(" Big Tuto {}/{} \r".format(i+1, nb_tutos)) + sys.stdout.flush() + + # Mini tutorials + for i in range(0, nb_tutos): + tuto = MiniTutorialFactory(title=fake.text(max_nb_chars=200), + description=fake.sentence(nb_words=15, variable_nb_words=True)) + tuto.authors.add(profiles[i % nb_users].user) + tuto.subcategory.add(sub_categories[random.randint(0, nb_sub_categories-1)]) + tutorials.append(tuto) + chap = ChapterFactory(tutorial=tutorials[j]) + nb_ext = randint(0, nb_avg_extracts_in_tuto * 2) + for l in range(0, nb_ext): + ExtractFactory(chapter=chap, + position_in_chapter=l, + title=fake.text(max_nb_chars=200)) + if i < int(nb_tutos * percent_tutos_validation_with_validator): + validator = staffs[random.randint(0, nb_staffs-1)] + v = TValidation(tutorial=tuto, + version=tuto.sha_draft, + date_proposition=datetime.now(), + date_reserve=datetime.now(), + validator=validator, + status="PENDING_V") + v.save() + tuto.sha_validation = tuto.sha_draft + tuto.save() + elif i < int(nb_tutos * (percent_tutos_validation_in_validation + + percent_tutos_validation_with_validator)): + v = TValidation(tutorial=tuto, + version=tuto.sha_draft, + date_proposition=datetime.now()) + v.save() + tuto.sha_validation = tuto.sha_draft + tuto.save() + elif i < int(nb_tutos * (percent_tutos_validation_in_validation + + percent_tutos_validation_with_validator + + percent_tutos_public)): + mep_tuto(tuto, tuto.sha_draft) + v = TValidation(tutorial=tuto, + version=tuto.sha_draft, + date_proposition=datetime.now(), + date_reserve=datetime.now(), + validator=validator, + status="ACCEPT", + comment_validator=fake.text(max_nb_chars=200), + date_validation=datetime.now()) + v.save() + tuto.sha_public = tuto.sha_draft + tuto.save() + sys.stdout.write(" Mini Tuto {}/{} \r".format(i+1, nb_tutos)) + sys.stdout.flush() + + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +def load_articles(cli, size, fake): + """ + Load articles + """ + + articles = [] + + nb_arts = size * 10 + percent_arts_validation_in_validation = 0.4 + percent_arts_validation_with_validator = 0.2 + percent_arts_public = 0.3 + cli.stdout.write(u"Nombres d'articles à créer : {}".format(nb_arts)) + cli.stdout.write(u"Nombres d'articles en validations : {}" + .format(str(int(nb_arts * percent_arts_validation_in_validation)))) + cli.stdout.write(u"Nombres d'articles réservé en validations : {}" + .format(str(int(nb_arts * percent_arts_validation_with_validator)))) + cli.stdout.write(u"Nombres d'articles publiés : {}" + .format(str(int(nb_arts * percent_arts_public)))) + tps1 = time.time() + nb_users = User.objects.count() + if nb_users == 0: + cli.stdout.write(u"Il n'y a aucun membre actuellement." + u"Vous devez rajouter les membre dans vos fixtures (member)") + else: + nb_sub_categories = SubCategory.objects.count() + if nb_sub_categories == 0: + cli.stdout.write(u"Il n'y a aucune catégories actuellement." + u"Vous devez rajouter les membre dans vos fixtures (category_content)") + else: + sub_categories = list(SubCategory.objects.all()) + profiles = list(Profile.objects.all()) + perms = list(Permission.objects.filter(codename__startswith='change_').all()) + staffs = list(User.objects.filter(groups__permissions__in=perms).all()) + nb_staffs = len(staffs) + if nb_staffs == 0: + cli.stdout.write(u"Il n'y a aucun staff actuellement." + u"Vous devez rajouter les staffs dans vos fixtures (staff)") + else: + for i in range(0, nb_arts): + art = ArticleFactory(title=fake.text(max_nb_chars=200), + description=fake.sentence(nb_words=15, variable_nb_words=True)) + art.authors.add(profiles[i % nb_users].user) + art.subcategory.add(sub_categories[random.randint(0, nb_sub_categories-1)]) + articles.append(art) + + if i < int(nb_arts * percent_arts_validation_with_validator): + validator = staffs[random.randint(0, nb_staffs-1)] + v = AValidation(article=art, + version=art.sha_draft, + date_proposition=datetime.now(), + date_reserve=datetime.now(), + validator=validator, + status="PENDING_V") + v.save() + art.sha_validation = art.sha_draft + art.save() + elif i < int(nb_arts * (percent_arts_validation_in_validation + + percent_arts_validation_with_validator)): + v = AValidation(article=art, + version=art.sha_draft, + date_proposition=datetime.now()) + v.save() + art.sha_validation = art.sha_draft + art.save() + elif i < int(nb_arts * (percent_arts_validation_in_validation + + percent_arts_validation_with_validator + + percent_arts_public)): + mep_art(art, art.sha_draft) + v = AValidation(article=art, + version=art.sha_draft, + date_proposition=datetime.now(), + date_reserve=datetime.now(), + validator=validator, + status="ACCEPT", + comment_validator=fake.text(max_nb_chars=200), + date_validation=datetime.now()) + v.save() + art.sha_public = art.sha_draft + art.pubdate = datetime.now() + art.save() + sys.stdout.write(" Article {}/{} \r".format(i+1, nb_arts)) + sys.stdout.flush() + + tps2 = time.time() + cli.stdout.write(u"\nFait en {} sec".format(tps2 - tps1)) + + +@transaction.atomic +class Command(BaseCommand): + args = 'size=[low|medium|high] type=member,staff,gallery,category_forum,category_content' + help = 'Load fixtures for ZdS' + # python manage.py load_fixtures size=low module=staff racine=user + + def handle(self, *args, **options): + default_size = "low" + default_root = "user" + default_module = ["member", + "staff", + "category_forum", + "category_content", + "forum", + "tag", + "topic", + "post", + "article", + "note", + "gallery", + "tutorial", + "reaction"] + for arg in args: + ps = arg.split("=") + if len(ps) < 2: + continue + else: + if ps[0] in ["size", "sizes", "taille", "level"]: + default_size = ps[1].split(",")[0] + elif ps[0] in ["type", "types"]: + default_module = ps[1].split(",") + elif ps[0] in ["racine"]: + default_root = ps[1].split(",")[0] + + if default_size == "low": + size = 1 + elif default_size == "medium": + size = 2 + elif default_size == "high": + size = 3 + else: + size = 1 + fake = Factory.create(locale="fr_FR") + + if "member" in default_module: + load_member(self, size, fake, default_root) + if "staff" in default_module: + load_staff(self, size, fake, default_root) + if "gallery" in default_module: + load_gallery(self, size, fake) + if "category_forum" in default_module: + load_categories_forum(self, size, fake) + if "forum" in default_module: + load_forums(self, size, fake) + if "tag" in default_module: + load_tags(self, size, fake) + if "topic" in default_module: + load_topics(self, size, fake) + if "post" in default_module: + load_posts(self, size, fake) + if "category_content" in default_module: + load_categories_content(self, size, fake) + if "article" in default_module: + load_articles(self, size, fake) + if "reaction" in default_module: + load_comment_article(self, size, fake) + if "tutorial" in default_module: + load_tutorials(self, size, fake) + if "note" in default_module: + load_comment_tutorial(self, size, fake) diff --git a/zds/utils/management/commands/pdf_generator.py b/zds/utils/management/commands/pdf_generator.py new file mode 100644 index 0000000000..0110f8bf92 --- /dev/null +++ b/zds/utils/management/commands/pdf_generator.py @@ -0,0 +1,42 @@ +# coding: utf-8 + +from django.core.management.base import BaseCommand +from zds.tutorial.models import Tutorial +from zds import settings +import os + + +class Command(BaseCommand): + args = 'id=1,2,3,4,5' + help = 'Generate tutorials pdfs' + # python manage.py pdf_generator id=3 + + def handle(self, *args, **options): + ids = [] + + for arg in args: + ps = arg.split("=") + if len(ps) < 2: + continue + else: + if ps[0] in ["id", "ids"]: + ids = ps[1].split(",") + + pandoc_debug_str = "" + if settings.PANDOC_LOG_STATE: + pandoc_debug_str = " 2>&1 | tee -a " + settings.PANDOC_LOG + + if len(ids) > 0: + tutorials = Tutorial.objects.filter(pk__in=ids, sha_public__isnull=False).all() + self.stdout.write(u"Génération de PDFs pour les tutoriels dont l'id est dans la liste : {}".format(ids)) + else: + tutorials = Tutorial.objects.filter(sha_public__isnull=False).all() + self.stdout.write(u"Génération de PDFs pour tous les tutoriels du site") + + for tutorial in tutorials: + prod_path = tutorial.get_prod_path(tutorial.sha_public) + os.system("cd "+prod_path + " && " + settings.PANDOC_LOC + "pandoc " + settings.PANDOC_PDF_PARAM + " " + + os.path.join(prod_path, tutorial.slug) + ".md " + + "-o " + os.path.join(prod_path, tutorial.slug) + + ".pdf" + pandoc_debug_str) + self.stdout.write(u"----> {}".format(tutorial.title)) diff --git a/zds/utils/migrations/0006_auto__add_field_category_position.py b/zds/utils/migrations/0006_auto__add_field_category_position.py new file mode 100644 index 0000000000..a501c29f64 --- /dev/null +++ b/zds/utils/migrations/0006_auto__add_field_category_position.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Category.position' + db.add_column(u'utils_category', 'position', + self.gf('django.db.models.fields.IntegerField')(default=0), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Category.position' + db.delete_column(u'utils_category', 'position') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'utils.alert': { + 'Meta': {'object_name': 'Alert'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'alerts'", 'to': u"orm['auth.User']"}), + 'comment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'alerts'", 'to': u"orm['utils.Comment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'scope': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + u'utils.category': { + 'Meta': {'object_name': 'Category'}, + 'description': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.categorysubcategory': { + 'Meta': {'object_name': 'CategorySubCategory'}, + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Category']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_main': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'subcategory': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.SubCategory']"}) + }, + u'utils.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': u"orm['auth.User']"}), + 'dislike': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments-editor'", 'null': 'True', 'to': u"orm['auth.User']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.CharField', [], {'max_length': '39'}), + 'is_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'like': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'position': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'text_hidden': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80'}), + 'text_html': ('django.db.models.fields.TextField', [], {}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'utils.commentdislike': { + 'Meta': {'object_name': 'CommentDislike'}, + 'comments': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Comment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_disliked'", 'to': u"orm['auth.User']"}) + }, + u'utils.commentlike': { + 'Meta': {'object_name': 'CommentLike'}, + 'comments': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Comment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_liked'", 'to': u"orm['auth.User']"}) + }, + u'utils.licence': { + 'Meta': {'object_name': 'Licence'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'description': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.subcategory': { + 'Meta': {'object_name': 'SubCategory'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subtitle': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.tag': { + 'Meta': {'object_name': 'Tag'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '20'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '20'}) + } + } + + complete_apps = ['utils'] \ No newline at end of file diff --git a/zds/utils/migrations/0006_auto__add_helpwriting.py b/zds/utils/migrations/0006_auto__add_helpwriting.py new file mode 100644 index 0000000000..23d128dcbb --- /dev/null +++ b/zds/utils/migrations/0006_auto__add_helpwriting.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'HelpWriting' + db.create_table(u'utils_helpwriting', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=20)), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=20)), + ('tablelabel', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('image', self.gf('django.db.models.fields.files.ImageField')(max_length=100)), + )) + db.send_create_signal(u'utils', ['HelpWriting']) + + + def backwards(self, orm): + # Deleting model 'HelpWriting' + db.delete_table(u'utils_helpwriting') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'utils.alert': { + 'Meta': {'object_name': 'Alert'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'alerts'", 'to': u"orm['auth.User']"}), + 'comment': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'alerts'", 'to': u"orm['utils.Comment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'scope': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + u'utils.category': { + 'Meta': {'object_name': 'Category'}, + 'description': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.categorysubcategory': { + 'Meta': {'object_name': 'CategorySubCategory'}, + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Category']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_main': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'subcategory': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.SubCategory']"}) + }, + u'utils.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': u"orm['auth.User']"}), + 'dislike': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments-editor'", 'null': 'True', 'to': u"orm['auth.User']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.CharField', [], {'max_length': '39'}), + 'is_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'like': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'position': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'text_hidden': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80'}), + 'text_html': ('django.db.models.fields.TextField', [], {}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'utils.commentdislike': { + 'Meta': {'object_name': 'CommentDislike'}, + 'comments': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Comment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_disliked'", 'to': u"orm['auth.User']"}) + }, + u'utils.commentlike': { + 'Meta': {'object_name': 'CommentLike'}, + 'comments': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Comment']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_liked'", 'to': u"orm['auth.User']"}) + }, + u'utils.helpwriting': { + 'Meta': {'object_name': 'HelpWriting'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '20'}), + 'tablelabel': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '20'}) + }, + u'utils.licence': { + 'Meta': {'object_name': 'Licence'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'description': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.subcategory': { + 'Meta': {'object_name': 'SubCategory'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subtitle': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.tag': { + 'Meta': {'object_name': 'Tag'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '20'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '20'}) + } + } + + complete_apps = ['utils'] \ No newline at end of file diff --git a/zds/utils/models.py b/zds/utils/models.py index 1c6c4e218e..2cac59ac53 100644 --- a/zds/utils/models.py +++ b/zds/utils/models.py @@ -10,6 +10,7 @@ from django.db import models from zds.utils import slugify from zds.utils.templatetags.emarkdown import emarkdown +from easy_thumbnails.fields import ThumbnailerImageField from model_utils.managers import InheritanceManager @@ -21,6 +22,13 @@ def image_path_category(instance, filename): return os.path.join('categorie/normal', str(instance.pk), filename) +def image_path_help(instance, filename): + """Return path to an image.""" + ext = filename.split('.')[-1] + filename = u'{}.{}'.format(str(uuid.uuid4()), string.lower(ext)) + return os.path.join('helps/normal', str(instance.pk), filename) + + class Category(models.Model): """Common category for several concepts of the application.""" @@ -30,6 +38,7 @@ class Meta: title = models.CharField('Titre', max_length=80) description = models.TextField('Description') + position = models.IntegerField('Position', default=0) slug = models.SlugField(max_length=80) @@ -290,3 +299,29 @@ def save(self, *args, **kwargs): self.title = smart_text(self.title).lower() self.slug = slugify(self.title) super(Tag, self).save(*args, **kwargs) + + +class HelpWriting(models.Model): + + """Tutorial Help""" + class Meta: + verbose_name = u'Aide à la rédaction' + verbose_name_plural = u'Aides à la rédaction' + + # A name for this help + title = models.CharField('Name', max_length=20, null=False) + slug = models.SlugField(max_length=20) + + # tablelabel: Used for the accessibility "This tutoriel need help for writing" + tablelabel = models.CharField('TableLabel', max_length=150, null=False) + + # The image to use to illustrate this role + image = ThumbnailerImageField(upload_to=image_path_help) + + def __unicode__(self): + """Textual Help Form.""" + return self.title + + def save(self, *args, **kwargs): + self.slug = slugify(self.title) + super(HelpWriting, self).save(*args, **kwargs) diff --git a/zds/utils/paginator.py b/zds/utils/paginator.py index ab83d2c5f9..66f18abaf1 100644 --- a/zds/utils/paginator.py +++ b/zds/utils/paginator.py @@ -1,34 +1,33 @@ # coding: utf-8 +from zds.settings import ZDS_APP def paginator_range(current, stop, start=1): assert(current <= stop) - # TODO: Rewrite this garbage, someday - # Basic case when no folding - if stop - start <= 4: + if stop - start <= ZDS_APP['paginator']['folding_limit']: return range(start, stop + 1) # Complex case when folding lst = [] - for i in range(start, stop + 1): + for page_number in range(start, stop + 1): # Bounds - if i == start or i == stop: - lst.append(i) - if i == start and not current - start <= 2: + if page_number == start or page_number == stop: + lst.append(page_number) + if page_number == start and current - start > 2: lst.append(None) # Neighbors - elif 0 < abs(i - current) <= 1: - lst.append(i) - if i - current > 0 and not stop - i <= 2: + elif abs(page_number - current) == 1: + lst.append(page_number) + if page_number - current > 0 and stop - page_number > 2: lst.append(None) # Current - elif i == current: - lst.append(i) - # LOL - elif i == stop - 1 and current == stop - 3: - lst.append(i) + elif page_number == current: + lst.append(page_number) + # Put some + elif page_number == stop - 1 and current == stop - 3: + lst.append(page_number) # And ignore all other numbers return lst diff --git a/zds/utils/templatetags/emarkdown.py b/zds/utils/templatetags/emarkdown.py index 0273941cee..b6bc0718fd 100644 --- a/zds/utils/templatetags/emarkdown.py +++ b/zds/utils/templatetags/emarkdown.py @@ -20,14 +20,14 @@ u"Veuillez rapporter le bug." -def get_markdown_instance(inline=False): +def get_markdown_instance(inline=False, js_support=False): """ Provide a pre-configured markdown parser. :param bool inline: If `True`, configure parser to parse only inline content. :return: A ZMarkdown parser. """ - zdsext = ZdsExtension({"inline": inline, "emoticons": smileys}) + zdsext = ZdsExtension({"inline": inline, "emoticons": smileys, "js_support": js_support}) # Generate parser md = markdown.Markdown(extensions=(zdsext,), safe_mode = 'escape', @@ -53,7 +53,7 @@ def get_markdown_instance(inline=False): return md -def render_markdown(text, inline=False): +def render_markdown(text, inline=False, js_support=False): """ Render a markdown text to html. @@ -62,11 +62,11 @@ def render_markdown(text, inline=False): :return: Equivalent html string. :rtype: str """ - return get_markdown_instance(inline=inline).convert(text).encode('utf-8').strip() + return get_markdown_instance(inline=inline, js_support=js_support).convert(text).encode('utf-8').strip() @register.filter(needs_autoescape=False) -def emarkdown(text): +def emarkdown(text, js=""): """ Filter markdown text and render it to html. @@ -74,8 +74,9 @@ def emarkdown(text): :return: Equivalent html string. :rtype: str """ + is_js = (js == "js") try: - return mark_safe(render_markdown(text, inline=False)) + return mark_safe(render_markdown(text, inline=False, js_support=is_js)) except: return mark_safe(u'

    {}

    '.format(__MD_ERROR_PARSING)) diff --git a/zds/utils/templatetags/topbar.py b/zds/utils/templatetags/topbar.py index eb6f6e518d..2b9f564b9b 100644 --- a/zds/utils/templatetags/topbar.py +++ b/zds/utils/templatetags/topbar.py @@ -1,5 +1,6 @@ # coding: utf-8 +from collections import OrderedDict from django import template from django.conf import settings import itertools @@ -61,23 +62,32 @@ def top_categories(user): @register.filter('top_categories_tuto') def top_categories_tuto(user): + """ + Get all the categories and their related subcategories + associed with an existing tutorial. The result is sorted + by alphabetic order. + """ + + # Ordered dict is use to keep order + cats = OrderedDict() - cats = {} subcats_tutos = Tutorial.objects.values('subcategory').filter(sha_public__isnull=False).all() catsubcats = CategorySubCategory.objects \ .filter(is_main=True)\ .filter(subcategory__in=subcats_tutos)\ + .order_by('category__position', 'subcategory__title')\ .select_related('subcategory', 'category')\ + .values('category__title', 'subcategory__title', 'subcategory__slug')\ .all() - cscs = list(catsubcats.all()) + for csc in catsubcats: + key = csc['category__title'] - for csc in cscs: - key = csc.category.title if key in cats: - cats[key].append(csc.subcategory) + cats[key].append((csc['subcategory__title'], csc['subcategory__slug'])) else: - cats[key] = [csc.subcategory] + cats[key] = [(csc['subcategory__title'], csc['subcategory__slug'])] + return cats diff --git a/zds/utils/tutorials.py b/zds/utils/tutorials.py index 3dfc505a66..3260f992ab 100644 --- a/zds/utils/tutorials.py +++ b/zds/utils/tutorials.py @@ -101,7 +101,7 @@ def get_blob(tree, chemin): if os.path.abspath(bl.path) == os.path.abspath(chemin): data = bl.data_stream.read() return data.decode('utf-8') - except: + except (OSError, IOError): return "" if len(tree.trees) > 0: for tr in tree.trees: @@ -435,10 +435,10 @@ def import_archive(request): import os try: import ujson as json_reader - except: + except ImportError: try: import simplejson as json_reader - except: + except ImportError: import json as json_reader archive = request.FILES["file"] @@ -501,7 +501,7 @@ def import_archive(request): except IOError: try: os.makedirs(ph_dest) - except: + except OSError: pass zfile.close()