From a7a6bd3a1c85b56c9d8bff801b986c7801097f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 5 Oct 2015 15:41:17 +0200 Subject: [PATCH] Migrate to django 1.8 and make taiga compatible with python 3.5 --- .travis.yml | 1 + CHANGELOG.md | 3 + requirements-devel.txt | 14 +- requirements.txt | 54 ++- settings/common.py | 39 +- settings/development.py | 5 +- settings/local.py.example | 2 + taiga/auth/services.py | 6 +- taiga/base/api/serializers.py | 2 +- taiga/base/api/views.py | 2 +- taiga/base/mails.py | 42 ++ taiga/base/management/commands/test_emails.py | 32 +- taiga/base/neighbors.py | 2 +- taiga/base/utils/contenttypes.py | 23 + taiga/events/signal_handlers.py | 2 + taiga/export_import/tasks.py | 14 +- taiga/feedback/services.py | 5 +- taiga/front/templatetags/functions.py | 5 +- taiga/mdrender/service.py | 10 +- taiga/mdrender/templatetags/functions.py | 4 +- .../history/templatetags/functions.py | 7 +- .../migrations/0006_remove_issue_watchers.py | 2 +- .../management/commands/sample_data.py | 1 - .../0002_remove_milestone_watchers.py | 2 +- .../migrations/0005_auto_20151005_1357.py | 25 ++ taiga/projects/notifications/models.py | 4 +- taiga/projects/notifications/services.py | 5 +- taiga/projects/services/invitations.py | 7 +- .../migrations/0008_remove_task_watchers.py | 2 +- .../0010_remove_userstory_watchers.py | 2 +- .../0002_remove_wikipage_watchers.py | 2 +- taiga/users/api.py | 10 +- taiga/users/forms.py | 2 +- .../migrations/0014_auto_20151005_1357.py | 31 ++ tests/integration/test_notifications.py | 401 ++++++++++++++---- tests/integration/test_timeline.py | 1 - tests/integration/test_users.py | 3 - tests/integration/test_userstories.py | 2 +- tests/unit/test_export.py | 1 - tests/unit/test_mdrender.py | 4 +- 40 files changed, 563 insertions(+), 218 deletions(-) create mode 100644 taiga/base/mails.py create mode 100644 taiga/base/utils/contenttypes.py create mode 100644 taiga/projects/notifications/migrations/0005_auto_20151005_1357.py create mode 100644 taiga/users/migrations/0014_auto_20151005_1357.py diff --git a/.travis.yml b/.travis.yml index d0bb13ca..4a94cfaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ sudo: false language: python python: - "3.4" + - "3.5" services: - rabbitmq # will start rabbitmq-server cache: diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a77eaf..465ef394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ ### Misc +- Made compatible with python 3.5. +- Migrated to django 1.8. +- Update the rest of requirements to the last version. - API: Mixin fields 'users', 'members' and 'memberships' in ProjectDetailSerializer. - API: Add stats/system resource with global server stats (total project, total users....) - API: Improve and fix some errors in issues/filters_data and userstories/filters_data. diff --git a/requirements-devel.txt b/requirements-devel.txt index df6cacac..bca12fc2 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -1,13 +1,13 @@ -r requirements.txt -factory_boy==2.4.1 -py==1.4.26 -pytest==2.6.4 -pytest-django==2.8.0 -pytest-pythonpath==0.6 +factory_boy==2.5.2 +py==1.4.30 +pytest==2.8.2 +pytest-django==2.9.1 +pytest-pythonpath==0.7 -coverage==3.7.1 -coveralls==0.4.2 +coverage==4.0 +coveralls==1.0 django-slowdown==0.0.1 transifex-client==0.11.1.beta diff --git a/requirements.txt b/requirements.txt index e116de5f..6d2df509 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,37 +1,35 @@ -Django==1.7.8 +Django==1.8.5 #djangorestframework==2.3.13 # It's not necessary since Taiga 1.7 -django-picklefield==0.3.1 -django-sampledatahelper==0.2.2 +django-picklefield==0.3.2 +django-sampledatahelper==0.3.0 gunicorn==19.3.0 -psycopg2==2.5.4 -pillow==2.5.3 -pytz==2014.4 -six==1.8.0 -amqp==1.4.6 +psycopg2==2.6.1 +Pillow==3.0.0 +pytz==2015.6 +six==1.10.0 +amqp==1.4.7 djmail==0.11 -django-pgjson==0.2.2 -djorm-pgarray==1.0.4 -django-jinja==1.0.4 -jinja2==2.7.2 -pygments==1.6 +django-pgjson==0.3.1 +djorm-pgarray==1.2 +django-jinja==1.4.1 +jinja2==2.8 +pygments==2.0.2 django-sites==0.8 -Markdown==2.4.1 -fn==0.2.13 +Markdown==2.6.2 +fn==0.4.3 diff-match-patch==20121119 -requests==2.4.1 +requests==2.8.0 django-sr==0.0.4 -easy-thumbnails==2.1 -celery==3.1.17 +easy-thumbnails==2.2 +celery==3.1.18 redis==2.10.3 -Unidecode==0.04.16 -raven==5.1.1 -bleach==1.4 -django-ipware==0.1.0 -premailer==2.8.1 +Unidecode==0.04.18 +raven==5.7.2 +bleach==1.4.2 +django-ipware==1.1.1 +premailer==2.9.6 +cssutils==1.0.1 # Compatible with python 3.5 django-transactional-cleanup==0.1.15 -lxml==3.4.1 +lxml==3.5.0b1 git+https://github.com/Xof/django-pglocks.git@dbb8d7375066859f897604132bd437832d2014ea -pyjwkest==1.0.3 - -# Comment it if you are using python >= 3.4 -enum34==1.0 +pyjwkest==1.0.5 diff --git a/settings/common.py b/settings/common.py index 9a256a45..37fb1e27 100644 --- a/settings/common.py +++ b/settings/common.py @@ -25,6 +25,8 @@ ADMINS = ( ("Admin", "example@example.com"), ) +DEBUG = False + DATABASES = { "default": { "ENGINE": "transaction_hooks.backends.postgresql_psycopg2", @@ -215,11 +217,29 @@ DEFAULT_FILE_STORAGE = "taiga.base.storage.FileSystemStorage" SECRET_KEY = "aw3+t2r(8(0kkrhg8)gx6i96v5^kv%6cfep9wxfom0%7dy0m9e" -TEMPLATE_LOADERS = [ - "django_jinja.loaders.AppLoader", - "django_jinja.loaders.FileSystemLoader", +TEMPLATES = [ + { + "BACKEND": "django_jinja.backend.Jinja2", + "DIRS": [ + os.path.join(BASE_DIR, "templates"), + ], + "APP_DIRS": True, + "OPTIONS": { + 'context_processors': [ + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.request", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + ], + "match_extension": ".jinja", + } + }, ] + MIDDLEWARE_CLASSES = [ "taiga.base.middleware.cors.CoorsMiddleware", "taiga.events.middleware.SessionIDMiddleware", @@ -234,22 +254,9 @@ MIDDLEWARE_CLASSES = [ "django.contrib.messages.middleware.MessageMiddleware", ] -TEMPLATE_CONTEXT_PROCESSORS = [ - "django.contrib.auth.context_processors.auth", - "django.core.context_processors.request", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "django.core.context_processors.static", - "django.core.context_processors.tz", - "django.contrib.messages.context_processors.messages", -] ROOT_URLCONF = "taiga.urls" -TEMPLATE_DIRS = [ - os.path.join(BASE_DIR, "templates"), -] - INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.contenttypes", diff --git a/settings/development.py b/settings/development.py index 008c128b..950ddd92 100644 --- a/settings/development.py +++ b/settings/development.py @@ -17,8 +17,5 @@ from .common import * DEBUG = True -TEMPLATE_DEBUG = DEBUG -TEMPLATE_CONTEXT_PROCESSORS += [ - "django.core.context_processors.debug", -] +TEMPLATES[0]["OPTIONS"]['context_processors'] += "django.template.context_processors.debug" diff --git a/settings/local.py.example b/settings/local.py.example index 95ce96fd..a6d16fab 100644 --- a/settings/local.py.example +++ b/settings/local.py.example @@ -16,6 +16,8 @@ from .development import * +#DEBUG = False + #ADMINS = ( # ("Admin", "example@example.com"), #) diff --git a/taiga/auth/services.py b/taiga/auth/services.py index 952bf6a7..55277a8f 100644 --- a/taiga/auth/services.py +++ b/taiga/auth/services.py @@ -28,9 +28,8 @@ from django.db import transaction as tx from django.db import IntegrityError from django.utils.translation import ugettext as _ -from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail - from taiga.base import exceptions as exc +from taiga.base.mails import mail_builder from taiga.users.serializers import UserAdminSerializer from taiga.users.services import get_and_validate_user @@ -57,8 +56,7 @@ def send_register_email(user) -> bool: """ cancel_token = get_token_for_user(user, "cancel_account") context = {"user": user, "cancel_token": cancel_token} - mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) - email = mbuilder.registered_user(user, context) + email = mail_builder.registered_user(user, context) return bool(email.send()) diff --git a/taiga/base/api/serializers.py b/taiga/base/api/serializers.py index 8add00ba..9a52e873 100644 --- a/taiga/base/api/serializers.py +++ b/taiga/base/api/serializers.py @@ -1005,7 +1005,7 @@ class ModelSerializer((six.with_metaclass(SerializerMetaclass, BaseSerializer))) m2m_data[field_name] = attrs.pop(field_name) # Forward m2m relations - for field in meta.many_to_many + meta.virtual_fields: + for field in list(meta.many_to_many) + meta.virtual_fields: if field.name in attrs: m2m_data[field.name] = attrs.pop(field.name) diff --git a/taiga/base/api/views.py b/taiga/base/api/views.py index 7a751eaf..293053da 100644 --- a/taiga/base/api/views.py +++ b/taiga/base/api/views.py @@ -447,7 +447,7 @@ class APIView(View): def api_server_error(request, *args, **kwargs): - if settings.DEBUG is False and request.META['CONTENT_TYPE'] == "application/json": + if settings.DEBUG is False and request.META.get('CONTENT_TYPE', None) == "application/json": return HttpResponse(json.dumps({"error": _("Server application error")}), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return server_error(request, *args, **kwargs) diff --git a/taiga/base/mails.py b/taiga/base/mails.py new file mode 100644 index 00000000..b078cbcf --- /dev/null +++ b/taiga/base/mails.py @@ -0,0 +1,42 @@ +# Copyright (C) 2015 Andrey Antukh +# Copyright (C) 2015 Jesús Espino +# Copyright (C) 2015 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.conf import settings + +from djmail import template_mail +import premailer + +import logging + + +# Hide CSS warnings messages if debug mode is disable +if not getattr(settings, "DEBUG", False): + premailer.premailer.cssutils.log.setLevel(logging.CRITICAL) + + +class InlineCSSTemplateMail(template_mail.TemplateMail): + def _render_message_body_as_html(self, context): + html = super()._render_message_body_as_html(context) + + # Transform CSS into line style attributes + return premailer.transform(html) + + +class MagicMailBuilder(template_mail.MagicMailBuilder): + pass + + +mail_builder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) diff --git a/taiga/base/management/commands/test_emails.py b/taiga/base/management/commands/test_emails.py index da535c7f..e0cd5e94 100644 --- a/taiga/base/management/commands/test_emails.py +++ b/taiga/base/management/commands/test_emails.py @@ -22,7 +22,7 @@ from django.db.models.loading import get_model from django.core.management.base import BaseCommand from django.utils import timezone -from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail +from taiga.base.mails import mail_builder from taiga.projects.models import Project, Membership from taiga.projects.history.models import HistoryEntry @@ -47,11 +47,12 @@ class Command(BaseCommand): locale = options.get('locale') test_email = args[0] - mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) - # Register email - context = {"lang": locale, "user": User.objects.all().order_by("?").first(), "cancel_token": "cancel-token"} - email = mbuilder.registered_user(test_email, context) + context = {"lang": locale, + "user": User.objects.all().order_by("?").first(), + "cancel_token": "cancel-token"} + + email = mail_builder.registered_user(test_email, context) email.send() # Membership invitation @@ -60,12 +61,13 @@ class Command(BaseCommand): membership.invitation_extra_text = "Text example, Text example,\nText example,\n\nText example" context = {"lang": locale, "membership": membership} - email = mbuilder.membership_invitation(test_email, context) + email = mail_builder.membership_invitation(test_email, context) email.send() # Membership notification - context = {"lang": locale, "membership": Membership.objects.order_by("?").filter(user__isnull=False).first()} - email = mbuilder.membership_notification(test_email, context) + context = {"lang": locale, + "membership": Membership.objects.order_by("?").filter(user__isnull=False).first()} + email = mail_builder.membership_notification(test_email, context) email.send() # Feedback @@ -81,17 +83,17 @@ class Command(BaseCommand): "key2": "value2", }, } - email = mbuilder.feedback_notification(test_email, context) + email = mail_builder.feedback_notification(test_email, context) email.send() # Password recovery context = {"lang": locale, "user": User.objects.all().order_by("?").first()} - email = mbuilder.password_recovery(test_email, context) + email = mail_builder.password_recovery(test_email, context) email.send() # Change email context = {"lang": locale, "user": User.objects.all().order_by("?").first()} - email = mbuilder.change_email(test_email, context) + email = mail_builder.change_email(test_email, context) email.send() # Export/Import emails @@ -102,7 +104,7 @@ class Command(BaseCommand): "error_subject": "Error generating project dump", "error_message": "Error generating project dump", } - email = mbuilder.export_error(test_email, context) + email = mail_builder.export_error(test_email, context) email.send() context = { "lang": locale, @@ -110,7 +112,7 @@ class Command(BaseCommand): "error_subject": "Error importing project dump", "error_message": "Error importing project dump", } - email = mbuilder.import_error(test_email, context) + email = mail_builder.import_error(test_email, context) email.send() deletion_date = timezone.now() + datetime.timedelta(seconds=60*60*24) @@ -121,7 +123,7 @@ class Command(BaseCommand): "project": Project.objects.all().order_by("?").first(), "deletion_date": deletion_date, } - email = mbuilder.dump_project(test_email, context) + email = mail_builder.dump_project(test_email, context) email.send() context = { @@ -129,7 +131,7 @@ class Command(BaseCommand): "user": User.objects.all().order_by("?").first(), "project": Project.objects.all().order_by("?").first(), } - email = mbuilder.load_dump(test_email, context) + email = mail_builder.load_dump(test_email, context) email.send() # Notification emails diff --git a/taiga/base/neighbors.py b/taiga/base/neighbors.py index 4e77c9cc..545c130c 100644 --- a/taiga/base/neighbors.py +++ b/taiga/base/neighbors.py @@ -43,7 +43,7 @@ def get_neighbors(obj, results_set=None): query = """ SELECT * FROM - (SELECT "id" as id, ROW_NUMBER() OVER() + (SELECT "col1" as id, ROW_NUMBER() OVER() FROM (%s) as ID_AND_ROW) AS SELECTED_ID_AND_ROW """ % (base_sql) diff --git a/taiga/base/utils/contenttypes.py b/taiga/base/utils/contenttypes.py new file mode 100644 index 00000000..1a8aabcf --- /dev/null +++ b/taiga/base/utils/contenttypes.py @@ -0,0 +1,23 @@ +# Copyright (C) 2015 Andrey Antukh +# Copyright (C) 2015 Jesús Espino +# Copyright (C) 2015 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.apps import apps +from django.contrib.contenttypes.management import update_contenttypes + + +def update_all_contenttypes(**kwargs): + for app_config in apps.get_app_configs(): + update_contenttypes(app_config, **kwargs) diff --git a/taiga/events/signal_handlers.py b/taiga/events/signal_handlers.py index 7f938f15..33b00e89 100644 --- a/taiga/events/signal_handlers.py +++ b/taiga/events/signal_handlers.py @@ -27,6 +27,8 @@ from . import events def on_save_any_model(sender, instance, created, **kwargs): # Ignore any object that can not have project_id + if not hasattr(instance, "project_id"): + return content_type = get_typename_for_model_instance(instance) # Ignore any other events diff --git a/taiga/export_import/tasks.py b/taiga/export_import/tasks.py index f833aef4..70a45f91 100644 --- a/taiga/export_import/tasks.py +++ b/taiga/export_import/tasks.py @@ -25,8 +25,7 @@ from django.utils import timezone from django.conf import settings from django.utils.translation import ugettext as _ -from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail - +from taiga.base.mails import mail_builder from taiga.celery import app from .service import render_project @@ -40,7 +39,6 @@ import resource @app.task(bind=True) def dump_project(self, user, project): - mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) path = "exports/{}/{}-{}.json".format(project.pk, project.slug, self.request.id) storage_path = default_storage.path(path) @@ -56,7 +54,7 @@ def dump_project(self, user, project): "error_message": _("Error generating project dump"), "project": project } - email = mbuilder.export_error(user, ctx) + email = mail_builder.export_error(user, ctx) email.send() logger.error('Error generating dump %s (by %s)', project.slug, user, exc_info=sys.exc_info()) return @@ -68,7 +66,7 @@ def dump_project(self, user, project): "user": user, "deletion_date": deletion_date } - email = mbuilder.dump_project(user, ctx) + email = mail_builder.dump_project(user, ctx) email.send() @@ -79,8 +77,6 @@ def delete_project_dump(project_id, project_slug, task_id): @app.task def load_project_dump(user, dump): - mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) - try: project = dict_to_project(dump, user.email) except Exception: @@ -89,11 +85,11 @@ def load_project_dump(user, dump): "error_subject": _("Error loading project dump"), "error_message": _("Error loading project dump"), } - email = mbuilder.import_error(user, ctx) + email = mail_builder.import_error(user, ctx) email.send() logger.error('Error loading dump %s (by %s)', project.slug, user, exc_info=sys.exc_info()) return ctx = {"user": user, "project": project} - email = mbuilder.load_dump(user, ctx) + email = mail_builder.load_dump(user, ctx) email.send() diff --git a/taiga/feedback/services.py b/taiga/feedback/services.py index e5f92c3c..a0f16762 100644 --- a/taiga/feedback/services.py +++ b/taiga/feedback/services.py @@ -16,7 +16,7 @@ from django.conf import settings -from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail +from taiga.base.mails import mail_builder def send_feedback(feedback_entry, extra, reply_to=[]): @@ -30,7 +30,6 @@ def send_feedback(feedback_entry, extra, reply_to=[]): "extra": extra } - mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) - email = mbuilder.feedback_notification(support_email, ctx) + email = mail_builder.feedback_notification(support_email, ctx) email.extra_headers["Reply-To"] = ", ".join(reply_to) email.send() diff --git a/taiga/front/templatetags/functions.py b/taiga/front/templatetags/functions.py index 9a510d50..3391fece 100644 --- a/taiga/front/templatetags/functions.py +++ b/taiga/front/templatetags/functions.py @@ -21,10 +21,7 @@ from django_sites import get_by_id as get_site_by_id from taiga.front.urls import urls -register = library.Library() - - -@register.global_function(name="resolve_front_url") +@library.global_function(name="resolve_front_url") def resolve(type, *args): site = get_site_by_id("front") url_tmpl = "{scheme}//{domain}{url}" diff --git a/taiga/mdrender/service.py b/taiga/mdrender/service.py index 077c0c63..a507b1c2 100644 --- a/taiga/mdrender/service.py +++ b/taiga/mdrender/service.py @@ -75,11 +75,11 @@ def _make_extensions_list(project=None): MentionsExtension(), TaigaReferencesExtension(project), TargetBlankLinkExtension(), - "extra", - "codehilite", - "sane_lists", - "toc", - "nl2br"] + "markdown.extensions.extra", + "markdown.extensions.codehilite", + "markdown.extensions.sane_lists", + "markdown.extensions.toc", + "markdown.extensions.nl2br"] import diff_match_patch diff --git a/taiga/mdrender/templatetags/functions.py b/taiga/mdrender/templatetags/functions.py index 7608f553..88c7af86 100644 --- a/taiga/mdrender/templatetags/functions.py +++ b/taiga/mdrender/templatetags/functions.py @@ -18,10 +18,8 @@ from django_jinja import library from jinja2 import Markup from taiga.mdrender.service import render -register = library.Library() - -@register.global_function +@library.global_function def mdrender(project, text) -> str: if text: return Markup(render(project, text)) diff --git a/taiga/projects/history/templatetags/functions.py b/taiga/projects/history/templatetags/functions.py index 8d616b73..eadda2c6 100644 --- a/taiga/projects/history/templatetags/functions.py +++ b/taiga/projects/history/templatetags/functions.py @@ -18,8 +18,6 @@ from django.utils.translation import ugettext_lazy as _ from django_jinja import library -register = library.Library() - EXTRA_FIELD_VERBOSE_NAMES = { "description_diff": _("description"), @@ -29,7 +27,7 @@ EXTRA_FIELD_VERBOSE_NAMES = { } -@register.global_function +@library.global_function def verbose_name(obj_class, field_name): if field_name in EXTRA_FIELD_VERBOSE_NAMES: return EXTRA_FIELD_VERBOSE_NAMES[field_name] @@ -39,6 +37,7 @@ def verbose_name(obj_class, field_name): except Exception: return field_name -@register.global_function + +@library.global_function def lists_diff(list1, list2): return (list(set(list1) - set(list2))) diff --git a/taiga/projects/issues/migrations/0006_remove_issue_watchers.py b/taiga/projects/issues/migrations/0006_remove_issue_watchers.py index 915ce22e..c612acb1 100644 --- a/taiga/projects/issues/migrations/0006_remove_issue_watchers.py +++ b/taiga/projects/issues/migrations/0006_remove_issue_watchers.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.db import connection from django.db import models, migrations from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes.management import update_all_contenttypes +from taiga.base.utils.contenttypes import update_all_contenttypes def create_notifications(apps, schema_editor): update_all_contenttypes(verbosity=0) diff --git a/taiga/projects/management/commands/sample_data.py b/taiga/projects/management/commands/sample_data.py index 26950c60..566bfe16 100644 --- a/taiga/projects/management/commands/sample_data.py +++ b/taiga/projects/management/commands/sample_data.py @@ -21,7 +21,6 @@ from django.core.management.base import BaseCommand from django.db import transaction from django.utils.timezone import now from django.conf import settings -from django.contrib.webdesign import lorem_ipsum from django.contrib.contenttypes.models import ContentType from sampledatahelper.helper import SampleDataHelper diff --git a/taiga/projects/milestones/migrations/0002_remove_milestone_watchers.py b/taiga/projects/milestones/migrations/0002_remove_milestone_watchers.py index 162b6c1c..0d8a3a61 100644 --- a/taiga/projects/milestones/migrations/0002_remove_milestone_watchers.py +++ b/taiga/projects/milestones/migrations/0002_remove_milestone_watchers.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.db import connection from django.db import models, migrations from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes.management import update_all_contenttypes +from taiga.base.utils.contenttypes import update_all_contenttypes def create_notifications(apps, schema_editor): update_all_contenttypes(verbosity=0) diff --git a/taiga/projects/notifications/migrations/0005_auto_20151005_1357.py b/taiga/projects/notifications/migrations/0005_auto_20151005_1357.py new file mode 100644 index 00000000..3d38d5e6 --- /dev/null +++ b/taiga/projects/notifications/migrations/0005_auto_20151005_1357.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0004_watched'), + ] + + operations = [ + migrations.AlterField( + model_name='historychangenotification', + name='history_entries', + field=models.ManyToManyField(verbose_name='history entries', to='history.HistoryEntry', related_name='+'), + ), + migrations.AlterField( + model_name='historychangenotification', + name='notify_users', + field=models.ManyToManyField(verbose_name='notify users', to=settings.AUTH_USER_MODEL, related_name='+'), + ), + ] diff --git a/taiga/projects/notifications/models.py b/taiga/projects/notifications/models.py index 603bdd85..6f224588 100644 --- a/taiga/projects/notifications/models.py +++ b/taiga/projects/notifications/models.py @@ -61,10 +61,10 @@ class HistoryChangeNotification(models.Model): verbose_name=_("created date time")) updated_datetime = models.DateTimeField(null=False, blank=False, auto_now_add=True, verbose_name=_("updated date time")) - history_entries = models.ManyToManyField("history.HistoryEntry", null=True, blank=True, + history_entries = models.ManyToManyField("history.HistoryEntry", verbose_name=_("history entries"), related_name="+") - notify_users = models.ManyToManyField("users.User", null=True, blank=True, + notify_users = models.ManyToManyField("users.User", verbose_name=_("notify users"), related_name="+") project = models.ForeignKey("projects.Project", null=False, blank=False, diff --git a/taiga/projects/notifications/services.py b/taiga/projects/notifications/services.py index e4bd8a3f..6b805170 100644 --- a/taiga/projects/notifications/services.py +++ b/taiga/projects/notifications/services.py @@ -28,9 +28,8 @@ from django.utils import timezone from django.conf import settings from django.utils.translation import ugettext as _ -from djmail import template_mail - from taiga.base import exceptions as exc +from taiga.base.mails import InlineCSSTemplateMail from taiga.projects.notifications.choices import NotifyLevel from taiga.projects.history.choices import HistoryType from taiga.projects.history.services import (make_key_from_model_object, @@ -202,7 +201,7 @@ def _make_template_mail(name:str): of it. """ cls = type("InlineCSSTemplateMail", - (template_mail.InlineCSSTemplateMail,), + (InlineCSSTemplateMail,), {"name": name}) return cls() diff --git a/taiga/projects/services/invitations.py b/taiga/projects/services/invitations.py index 4196612c..50e1e01e 100644 --- a/taiga/projects/services/invitations.py +++ b/taiga/projects/services/invitations.py @@ -1,17 +1,16 @@ from django.apps import apps from django.conf import settings -from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail +from taiga.base.mails import mail_builder def send_invitation(invitation): """Send an invitation email""" - mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) if invitation.user: - template = mbuilder.membership_notification + template = mail_builder.membership_notification email = template(invitation.user, {"membership": invitation}) else: - template = mbuilder.membership_invitation + template = mail_builder.membership_invitation email = template(invitation.email, {"membership": invitation}) email.send() diff --git a/taiga/projects/tasks/migrations/0008_remove_task_watchers.py b/taiga/projects/tasks/migrations/0008_remove_task_watchers.py index 5a8b0618..639e12b1 100644 --- a/taiga/projects/tasks/migrations/0008_remove_task_watchers.py +++ b/taiga/projects/tasks/migrations/0008_remove_task_watchers.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.db import connection from django.db import models, migrations from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes.management import update_all_contenttypes +from taiga.base.utils.contenttypes import update_all_contenttypes def create_notifications(apps, schema_editor): update_all_contenttypes(verbosity=0) diff --git a/taiga/projects/userstories/migrations/0010_remove_userstory_watchers.py b/taiga/projects/userstories/migrations/0010_remove_userstory_watchers.py index e2ff7538..e3b94599 100644 --- a/taiga/projects/userstories/migrations/0010_remove_userstory_watchers.py +++ b/taiga/projects/userstories/migrations/0010_remove_userstory_watchers.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.db import connection from django.db import models, migrations from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes.management import update_all_contenttypes +from taiga.base.utils.contenttypes import update_all_contenttypes def create_notifications(apps, schema_editor): update_all_contenttypes(verbosity=0) diff --git a/taiga/projects/wiki/migrations/0002_remove_wikipage_watchers.py b/taiga/projects/wiki/migrations/0002_remove_wikipage_watchers.py index 7d7af0b6..44d6d5ac 100644 --- a/taiga/projects/wiki/migrations/0002_remove_wikipage_watchers.py +++ b/taiga/projects/wiki/migrations/0002_remove_wikipage_watchers.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.db import connection from django.db import models, migrations from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes.management import update_all_contenttypes +from taiga.base.utils.contenttypes import update_all_contenttypes def create_notifications(apps, schema_editor): update_all_contenttypes(verbosity=0) diff --git a/taiga/users/api.py b/taiga/users/api.py index 1d41d5e3..b635b1cd 100644 --- a/taiga/users/api.py +++ b/taiga/users/api.py @@ -33,13 +33,11 @@ from taiga.base.api import ModelCrudViewSet from taiga.base.filters import PermissionBasedFilterBackend from taiga.base.api.utils import get_object_or_404 from taiga.base.filters import MembersFilterBackend +from taiga.base.mails import mail_builder from taiga.projects.votes import services as votes_service from easy_thumbnails.source_generators import pil_image -from djmail.template_mail import MagicMailBuilder -from djmail.template_mail import InlineCSSTemplateMail - from . import models from . import serializers from . import permissions @@ -189,8 +187,7 @@ class UsersViewSet(ModelCrudViewSet): user.token = str(uuid.uuid1()) user.save(update_fields=["token"]) - mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) - email = mbuilder.password_recovery(user, {"user": user}) + email = mail_builder.password_recovery(user, {"user": user}) email.send() return response.Ok({"detail": _("Mail sended successful!")}) @@ -314,8 +311,7 @@ class UsersViewSet(ModelCrudViewSet): request.user.email_token = str(uuid.uuid1()) request.user.new_email = new_email request.user.save(update_fields=["email_token", "new_email"]) - mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) - email = mbuilder.change_email(request.user.new_email, {"user": request.user, + email = mail_builder.change_email(request.user.new_email, {"user": request.user, "lang": request.user.lang}) email.send() diff --git a/taiga/users/forms.py b/taiga/users/forms.py index 1e7bb7ab..a5312c6a 100644 --- a/taiga/users/forms.py +++ b/taiga/users/forms.py @@ -41,4 +41,4 @@ class UserCreationForm(DjangoUserCreationForm): class UserChangeForm(DjangoUserChangeForm): class Meta: model = User - + fields = '__all__' diff --git a/taiga/users/migrations/0014_auto_20151005_1357.py b/taiga/users/migrations/0014_auto_20151005_1357.py new file mode 100644 index 00000000..ee24e169 --- /dev/null +++ b/taiga/users/migrations/0014_auto_20151005_1357.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.contrib.auth.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0013_auto_20150901_1600'), + ] + + operations = [ + migrations.AlterModelManagers( + name='user', + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.AlterField( + model_name='user', + name='last_login', + field=models.DateTimeField(verbose_name='last login', blank=True, null=True), + ), + migrations.AlterField( + model_name='user', + name='new_email', + field=models.EmailField(verbose_name='new email address', blank=True, null=True, max_length=254), + ), + ] diff --git a/tests/integration/test_notifications.py b/tests/integration/test_notifications.py index d183a9ef..7626769b 100644 --- a/tests/integration/test_notifications.py +++ b/tests/integration/test_notifications.py @@ -355,7 +355,7 @@ def test_watching_users_to_notify_on_issue_modification_6(): assert users == {watching_user, issue.owner} -def test_send_notifications_using_services_method(settings, mail): +def test_send_notifications_using_services_method_for_user_stories(settings, mail): settings.CHANGE_NOTIFICATIONS_MIN_INTERVAL = 1 project = f.ProjectFactory.create() @@ -363,38 +363,34 @@ def test_send_notifications_using_services_method(settings, mail): member1 = f.MembershipFactory.create(project=project, role=role) member2 = f.MembershipFactory.create(project=project, role=role) - history_change = MagicMock() - history_change.user = {"pk": member1.user.pk} - history_change.comment = "" - history_change.type = HistoryType.change - history_change.is_hidden = False - - history_create = MagicMock() - history_create.user = {"pk": member1.user.pk} - history_create.comment = "" - history_create.type = HistoryType.create - history_create.is_hidden = False - - history_delete = MagicMock() - history_delete.user = {"pk": member1.user.pk} - history_delete.comment = "" - history_delete.type = HistoryType.delete - history_delete.is_hidden = False - - # Issues - issue = f.IssueFactory.create(project=project, owner=member2.user) - take_snapshot(issue, user=issue.owner) - services.send_notifications(issue, - history=history_create) - - services.send_notifications(issue, - history=history_change) - - services.send_notifications(issue, - history=history_delete) - - # Userstories us = f.UserStoryFactory.create(project=project, owner=member2.user) + history_change = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="", + type=HistoryType.change, + key="userstories.userstory:{}".format(us.id), + is_hidden=False, + diff=[] + ) + + history_create = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="", + type=HistoryType.create, + key="userstories.userstory:{}".format(us.id), + is_hidden=False, + diff=[] + ) + + history_delete = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="test:delete", + type=HistoryType.delete, + key="userstories.userstory:{}".format(us.id), + is_hidden=False, + diff=[] + ) + take_snapshot(us, user=us.owner) services.send_notifications(us, history=history_create) @@ -405,63 +401,25 @@ def test_send_notifications_using_services_method(settings, mail): services.send_notifications(us, history=history_delete) - # Tasks - task = f.TaskFactory.create(project=project, owner=member2.user) - take_snapshot(task, user=task.owner) - services.send_notifications(task, - history=history_create) - - services.send_notifications(task, - history=history_change) - - services.send_notifications(task, - history=history_delete) - - # Wiki pages - wiki = f.WikiPageFactory.create(project=project, owner=member2.user) - take_snapshot(wiki, user=wiki.owner) - services.send_notifications(wiki, - history=history_create) - - services.send_notifications(wiki, - history=history_change) - - services.send_notifications(wiki, - history=history_delete) - - assert models.HistoryChangeNotification.objects.count() == 12 + assert models.HistoryChangeNotification.objects.count() == 3 assert len(mail.outbox) == 0 time.sleep(1) services.process_sync_notifications() - assert len(mail.outbox) == 12 + assert len(mail.outbox) == 3 # test headers - events = [issue, us, task, wiki] domain = settings.SITES["api"]["domain"].split(":")[0] or settings.SITES["api"]["domain"] - i = 0 for msg in mail.outbox: - # each event has 3 msgs - event = events[math.floor(i / 3)] + m_id = "{project_slug}/{msg_id}".format( + project_slug=project.slug, + msg_id=us.ref + ) - # each set of 3 should have the same headers - if i % 3 == 0: - if hasattr(event, 'ref'): - e_slug = event.ref - elif hasattr(event, 'slug'): - e_slug = event.slug - else: - e_slug = 'taiga-system' - - m_id = "{project_slug}/{msg_id}".format( - project_slug=project.slug, - msg_id=e_slug - ) - - message_id = "<{m_id}/".format(m_id=m_id) - message_id_domain = "@{domain}>".format(domain=domain) - in_reply_to = "<{m_id}@{domain}>".format(m_id=m_id, domain=domain) - list_id = "Taiga/{p_name} " \ - .format(p_name=project.name, p_slug=project.slug, domain=domain) + message_id = "<{m_id}/".format(m_id=m_id) + message_id_domain = "@{domain}>".format(domain=domain) + in_reply_to = "<{m_id}@{domain}>".format(m_id=m_id, domain=domain) + list_id = "Taiga/{p_name} " \ + .format(p_name=project.name, p_slug=project.slug, domain=domain) assert msg.extra_headers headers = msg.extra_headers @@ -490,7 +448,286 @@ def test_send_notifications_using_services_method(settings, mail): msg_ts = datetime.datetime.fromtimestamp(int(msg_time)) assert services.make_ms_thread_index(in_reply_to, msg_ts) == headers.get('Thread-Index') - i += 1 + +def test_send_notifications_using_services_method_for_tasks(settings, mail): + settings.CHANGE_NOTIFICATIONS_MIN_INTERVAL = 1 + + project = f.ProjectFactory.create() + role = f.RoleFactory.create(project=project, permissions=['view_issues', 'view_us', 'view_tasks', 'view_wiki_pages']) + member1 = f.MembershipFactory.create(project=project, role=role) + member2 = f.MembershipFactory.create(project=project, role=role) + + task = f.TaskFactory.create(project=project, owner=member2.user) + history_change = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="", + type=HistoryType.change, + key="tasks.task:{}".format(task.id), + is_hidden=False, + diff=[] + ) + + history_create = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="", + type=HistoryType.create, + key="tasks.task:{}".format(task.id), + is_hidden=False, + diff=[] + ) + + history_delete = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="test:delete", + type=HistoryType.delete, + key="tasks.task:{}".format(task.id), + is_hidden=False, + diff=[] + ) + + take_snapshot(task, user=task.owner) + services.send_notifications(task, + history=history_create) + + services.send_notifications(task, + history=history_change) + + services.send_notifications(task, + history=history_delete) + + assert models.HistoryChangeNotification.objects.count() == 3 + assert len(mail.outbox) == 0 + time.sleep(1) + services.process_sync_notifications() + assert len(mail.outbox) == 3 + + # test headers + domain = settings.SITES["api"]["domain"].split(":")[0] or settings.SITES["api"]["domain"] + for msg in mail.outbox: + m_id = "{project_slug}/{msg_id}".format( + project_slug=project.slug, + msg_id=task.ref + ) + + message_id = "<{m_id}/".format(m_id=m_id) + message_id_domain = "@{domain}>".format(domain=domain) + in_reply_to = "<{m_id}@{domain}>".format(m_id=m_id, domain=domain) + list_id = "Taiga/{p_name} " \ + .format(p_name=project.name, p_slug=project.slug, domain=domain) + + assert msg.extra_headers + headers = msg.extra_headers + + # can't test the time part because it's set when sending + # check what we can + assert 'Message-ID' in headers + assert message_id in headers.get('Message-ID') + assert message_id_domain in headers.get('Message-ID') + + assert 'In-Reply-To' in headers + assert in_reply_to == headers.get('In-Reply-To') + assert 'References' in headers + assert in_reply_to == headers.get('References') + + assert 'List-ID' in headers + assert list_id == headers.get('List-ID') + + assert 'Thread-Index' in headers + # always is b64 encoded 22 bytes + assert len(base64.b64decode(headers.get('Thread-Index'))) == 22 + + # hashes should match for identical ids and times + # we check the actual method in test_ms_thread_id() + msg_time = headers.get('Message-ID').split('/')[2].split('@')[0] + msg_ts = datetime.datetime.fromtimestamp(int(msg_time)) + assert services.make_ms_thread_index(in_reply_to, msg_ts) == headers.get('Thread-Index') + + +def test_send_notifications_using_services_method_for_issues(settings, mail): + settings.CHANGE_NOTIFICATIONS_MIN_INTERVAL = 1 + + project = f.ProjectFactory.create() + role = f.RoleFactory.create(project=project, permissions=['view_issues', 'view_us', 'view_tasks', 'view_wiki_pages']) + member1 = f.MembershipFactory.create(project=project, role=role) + member2 = f.MembershipFactory.create(project=project, role=role) + + issue = f.IssueFactory.create(project=project, owner=member2.user) + history_change = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="", + type=HistoryType.change, + key="issues.issue:{}".format(issue.id), + is_hidden=False, + diff=[] + ) + + history_create = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="", + type=HistoryType.create, + key="issues.issue:{}".format(issue.id), + is_hidden=False, + diff=[] + ) + + history_delete = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="test:delete", + type=HistoryType.delete, + key="issues.issue:{}".format(issue.id), + is_hidden=False, + diff=[] + ) + + take_snapshot(issue, user=issue.owner) + services.send_notifications(issue, + history=history_create) + + services.send_notifications(issue, + history=history_change) + + services.send_notifications(issue, + history=history_delete) + + assert models.HistoryChangeNotification.objects.count() == 3 + assert len(mail.outbox) == 0 + time.sleep(1) + services.process_sync_notifications() + assert len(mail.outbox) == 3 + + # test headers + domain = settings.SITES["api"]["domain"].split(":")[0] or settings.SITES["api"]["domain"] + for msg in mail.outbox: + m_id = "{project_slug}/{msg_id}".format( + project_slug=project.slug, + msg_id=issue.ref + ) + + message_id = "<{m_id}/".format(m_id=m_id) + message_id_domain = "@{domain}>".format(domain=domain) + in_reply_to = "<{m_id}@{domain}>".format(m_id=m_id, domain=domain) + list_id = "Taiga/{p_name} " \ + .format(p_name=project.name, p_slug=project.slug, domain=domain) + + assert msg.extra_headers + headers = msg.extra_headers + + # can't test the time part because it's set when sending + # check what we can + assert 'Message-ID' in headers + assert message_id in headers.get('Message-ID') + assert message_id_domain in headers.get('Message-ID') + + assert 'In-Reply-To' in headers + assert in_reply_to == headers.get('In-Reply-To') + assert 'References' in headers + assert in_reply_to == headers.get('References') + + assert 'List-ID' in headers + assert list_id == headers.get('List-ID') + + assert 'Thread-Index' in headers + # always is b64 encoded 22 bytes + assert len(base64.b64decode(headers.get('Thread-Index'))) == 22 + + # hashes should match for identical ids and times + # we check the actual method in test_ms_thread_id() + msg_time = headers.get('Message-ID').split('/')[2].split('@')[0] + msg_ts = datetime.datetime.fromtimestamp(int(msg_time)) + assert services.make_ms_thread_index(in_reply_to, msg_ts) == headers.get('Thread-Index') + + +def test_send_notifications_using_services_method_for_wiki_pages(settings, mail): + settings.CHANGE_NOTIFICATIONS_MIN_INTERVAL = 1 + + project = f.ProjectFactory.create() + role = f.RoleFactory.create(project=project, permissions=['view_issues', 'view_us', 'view_tasks', 'view_wiki_pages']) + member1 = f.MembershipFactory.create(project=project, role=role) + member2 = f.MembershipFactory.create(project=project, role=role) + + wiki = f.WikiPageFactory.create(project=project, owner=member2.user) + history_change = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="", + type=HistoryType.change, + key="wiki.wikipage:{}".format(wiki.id), + is_hidden=False, + diff=[] + ) + + history_create = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="", + type=HistoryType.create, + key="wiki.wikipage:{}".format(wiki.id), + is_hidden=False, + diff=[] + ) + + history_delete = f.HistoryEntryFactory.create( + user={"pk": member1.user.id}, + comment="test:delete", + type=HistoryType.delete, + key="wiki.wikipage:{}".format(wiki.id), + is_hidden=False, + diff=[] + ) + take_snapshot(wiki, user=wiki.owner) + services.send_notifications(wiki, + history=history_create) + + services.send_notifications(wiki, + history=history_change) + + services.send_notifications(wiki, + history=history_delete) + + assert models.HistoryChangeNotification.objects.count() == 3 + assert len(mail.outbox) == 0 + time.sleep(1) + services.process_sync_notifications() + assert len(mail.outbox) == 3 + + # test headers + domain = settings.SITES["api"]["domain"].split(":")[0] or settings.SITES["api"]["domain"] + for msg in mail.outbox: + m_id = "{project_slug}/{msg_id}".format( + project_slug=project.slug, + msg_id=wiki.slug + ) + + message_id = "<{m_id}/".format(m_id=m_id) + message_id_domain = "@{domain}>".format(domain=domain) + in_reply_to = "<{m_id}@{domain}>".format(m_id=m_id, domain=domain) + list_id = "Taiga/{p_name} " \ + .format(p_name=project.name, p_slug=project.slug, domain=domain) + + assert msg.extra_headers + headers = msg.extra_headers + + # can't test the time part because it's set when sending + # check what we can + assert 'Message-ID' in headers + assert message_id in headers.get('Message-ID') + assert message_id_domain in headers.get('Message-ID') + + assert 'In-Reply-To' in headers + assert in_reply_to == headers.get('In-Reply-To') + assert 'References' in headers + assert in_reply_to == headers.get('References') + + assert 'List-ID' in headers + assert list_id == headers.get('List-ID') + + assert 'Thread-Index' in headers + # always is b64 encoded 22 bytes + assert len(base64.b64decode(headers.get('Thread-Index'))) == 22 + + # hashes should match for identical ids and times + # we check the actual method in test_ms_thread_id() + msg_time = headers.get('Message-ID').split('/')[2].split('@')[0] + msg_ts = datetime.datetime.fromtimestamp(int(msg_time)) + assert services.make_ms_thread_index(in_reply_to, msg_ts) == headers.get('Thread-Index') def test_resource_notification_test(client, settings, mail): diff --git a/tests/integration/test_timeline.py b/tests/integration/test_timeline.py index 582c3667..b526ac76 100644 --- a/tests/integration/test_timeline.py +++ b/tests/integration/test_timeline.py @@ -200,7 +200,6 @@ def test_update_project_timeline(): project = factories.ProjectFactory.create(name="test project timeline") history_services.take_snapshot(project, user=project.owner) project.add_watcher(user_watcher) - print("PPPP") project.name = "test project timeline updated" project.save() history_services.take_snapshot(project, user=project.owner) diff --git a/tests/integration/test_users.py b/tests/integration/test_users.py index 302a51a3..a9d4c1cd 100644 --- a/tests/integration/test_users.py +++ b/tests/integration/test_users.py @@ -483,9 +483,6 @@ def test_get_voted_list_valid_info_for_project(): assert project_vote_info["is_private"] == project.is_private - import pprint - pprint.pprint(project_vote_info) - assert project_vote_info["is_fan"] == False assert project_vote_info["is_watcher"] == False assert project_vote_info["total_watchers"] == 0 diff --git a/tests/integration/test_userstories.py b/tests/integration/test_userstories.py index 6506f70f..4ddaece0 100644 --- a/tests/integration/test_userstories.py +++ b/tests/integration/test_userstories.py @@ -57,7 +57,7 @@ def test_create_userstory_with_watchers(client): data = {"subject": "Test user story", "project": project.id, "watchers": [user_watcher.id]} client.login(user) response = client.json.post(url, json.dumps(data)) - print(response.data) + assert response.status_code == 201 assert response.data["watchers"] == [] diff --git a/tests/unit/test_export.py b/tests/unit/test_export.py index 0a6ba9d6..f95e44bd 100644 --- a/tests/unit/test_export.py +++ b/tests/unit/test_export.py @@ -28,7 +28,6 @@ def test_export_issue_finish_date(client): issue = f.IssueFactory.create(finished_date="2014-10-22") output = io.StringIO() render_project(issue.project, output) - print(output.getvalue()) project_data = json.loads(output.getvalue()) finish_date = project_data["issues"][0]["finished_date"] assert finish_date == "2014-10-22T00:00:00+0000" diff --git a/tests/unit/test_mdrender.py b/tests/unit/test_mdrender.py index 1de084d8..d0404c13 100644 --- a/tests/unit/test_mdrender.py +++ b/tests/unit/test_mdrender.py @@ -165,8 +165,8 @@ def test_render_relative_image(): def test_render_triple_quote_code(): - expected_result = "
print(\"test\")\n
" - assert render(dummy_project, "```\nprint(\"test\")\n```") == expected_result + expected_result = "
print(\"test\")\n
" + assert render(dummy_project, "```python\nprint(\"test\")\n```") == expected_result def test_render_triple_quote_and_lang_code():