Migrate to django 1.8 and make taiga compatible with python 3.5
parent
9226913caa
commit
a7a6bd3a1c
|
@ -2,6 +2,7 @@ sudo: false
|
|||
language: python
|
||||
python:
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
services:
|
||||
- rabbitmq # will start rabbitmq-server
|
||||
cache:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
from .development import *
|
||||
|
||||
#DEBUG = False
|
||||
|
||||
#ADMINS = (
|
||||
# ("Admin", "example@example.com"),
|
||||
#)
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
|
||||
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
|
||||
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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='+'),
|
||||
),
|
||||
]
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -41,4 +41,4 @@ class UserCreationForm(DjangoUserCreationForm):
|
|||
class UserChangeForm(DjangoUserChangeForm):
|
||||
class Meta:
|
||||
model = User
|
||||
|
||||
fields = '__all__'
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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,56 +401,18 @@ 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)]
|
||||
|
||||
# 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
|
||||
msg_id=us.ref
|
||||
)
|
||||
|
||||
message_id = "<{m_id}/".format(m_id=m_id)
|
||||
|
@ -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} <taiga.{p_slug}@{domain}>" \
|
||||
.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} <taiga.{p_slug}@{domain}>" \
|
||||
.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} <taiga.{p_slug}@{domain}>" \
|
||||
.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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"] == []
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -165,8 +165,8 @@ def test_render_relative_image():
|
|||
|
||||
|
||||
def test_render_triple_quote_code():
|
||||
expected_result = "<div class=\"codehilite\"><pre><span class=\"n\">print</span><span class=\"p\">(</span><span class=\"s\">\"test\"</span><span class=\"p\">)</span>\n</pre></div>"
|
||||
assert render(dummy_project, "```\nprint(\"test\")\n```") == expected_result
|
||||
expected_result = "<div class=\"codehilite\"><pre><span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"test\"</span><span class=\"p\">)</span>\n</pre></div>"
|
||||
assert render(dummy_project, "```python\nprint(\"test\")\n```") == expected_result
|
||||
|
||||
|
||||
def test_render_triple_quote_and_lang_code():
|
||||
|
|
Loading…
Reference in New Issue