Update django to 1.9 and the rest of requirements
parent
010fcfa635
commit
7b7548b47d
|
@ -1,8 +1,8 @@
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
factory_boy==2.6.0
|
factory_boy==2.6.1
|
||||||
py==1.4.31
|
py==1.4.31
|
||||||
pytest==2.8.5
|
pytest==2.8.7
|
||||||
pytest-django==2.9.1
|
pytest-django==2.9.1
|
||||||
pytest-pythonpath==0.7
|
pytest-pythonpath==0.7
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,36 @@
|
||||||
Django==1.8.6
|
Django==1.9.2
|
||||||
#djangorestframework==2.3.13 # It's not necessary since Taiga 1.7
|
#djangorestframework==2.3.13 # It's not necessary since Taiga 1.7
|
||||||
django-picklefield==0.3.2
|
django-picklefield==0.3.2
|
||||||
django-sampledatahelper==0.3.0
|
django-sampledatahelper==0.4.0
|
||||||
gunicorn==19.3.0
|
gunicorn==19.4.5
|
||||||
psycopg2==2.6.1
|
psycopg2==2.6.1
|
||||||
Pillow==2.9.0
|
Pillow==3.1.1
|
||||||
pytz==2015.7
|
pytz==2015.7
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
amqp==1.4.7
|
amqp==1.4.9
|
||||||
djmail==0.11
|
djmail==0.12.0.post1
|
||||||
django-pgjson==0.3.1
|
django-pgjson==0.3.1
|
||||||
djorm-pgarray==1.2
|
djorm-pgarray==1.2
|
||||||
django-jinja==2.1.1
|
django-jinja==2.1.2
|
||||||
jinja2==2.8
|
jinja2==2.8
|
||||||
pygments==2.0.2
|
pygments==2.0.2
|
||||||
django-sites==0.8
|
django-sites==0.9
|
||||||
Markdown==2.6.5
|
Markdown==2.6.5
|
||||||
fn==0.4.3
|
fn==0.4.3
|
||||||
diff-match-patch==20121119
|
diff-match-patch==20121119
|
||||||
requests==2.8.1
|
requests==2.9.1
|
||||||
django-sr==0.0.4
|
django-sr==0.0.4
|
||||||
easy-thumbnails==2.2.1
|
easy-thumbnails==2.3
|
||||||
celery==3.1.19
|
celery==3.1.20
|
||||||
redis==2.10.5
|
redis==2.10.5
|
||||||
Unidecode==0.04.18
|
Unidecode==0.04.19
|
||||||
raven==5.9.2
|
raven==5.10.2
|
||||||
bleach==1.4.2
|
bleach==1.4.2
|
||||||
django-ipware==1.1.2
|
django-ipware==1.1.3
|
||||||
premailer==2.9.6
|
premailer==2.9.7
|
||||||
cssutils==1.0.1 # Compatible with python 3.5
|
cssutils==1.0.1 # Compatible with python 3.5
|
||||||
django-transactional-cleanup==0.1.15
|
|
||||||
lxml==3.5.0
|
lxml==3.5.0
|
||||||
git+https://github.com/Xof/django-pglocks.git@dbb8d7375066859f897604132bd437832d2014ea
|
git+https://github.com/Xof/django-pglocks.git@dbb8d7375066859f897604132bd437832d2014ea
|
||||||
pyjwkest==1.0.9
|
pyjwkest==1.1.5
|
||||||
python-dateutil==2.4.2
|
python-dateutil==2.4.2
|
||||||
netaddr==0.7.18
|
netaddr==0.7.18
|
||||||
|
|
|
@ -30,7 +30,7 @@ DEBUG = False
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "transaction_hooks.backends.postgresql_psycopg2",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"NAME": "taiga",
|
"NAME": "taiga",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,7 +320,6 @@ INSTALLED_APPS = [
|
||||||
"sr",
|
"sr",
|
||||||
"easy_thumbnails",
|
"easy_thumbnails",
|
||||||
"raven.contrib.django.raven_compat",
|
"raven.contrib.django.raven_compat",
|
||||||
"django_transactional_cleanup",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = "taiga.wsgi.application"
|
WSGI_APPLICATION = "taiga.wsgi.application"
|
||||||
|
@ -347,7 +346,7 @@ LOGGING = {
|
||||||
"handlers": {
|
"handlers": {
|
||||||
"null": {
|
"null": {
|
||||||
"level":"DEBUG",
|
"level":"DEBUG",
|
||||||
"class":"django.utils.log.NullHandler",
|
"class":"logging.NullHandler",
|
||||||
},
|
},
|
||||||
"console":{
|
"console":{
|
||||||
"level":"DEBUG",
|
"level":"DEBUG",
|
||||||
|
|
|
@ -24,7 +24,7 @@ from .development import *
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'transaction_hooks.backends.postgresql_psycopg2',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
'NAME': 'taiga',
|
'NAME': 'taiga',
|
||||||
'USER': 'taiga',
|
'USER': 'taiga',
|
||||||
'PASSWORD': 'changeme',
|
'PASSWORD': 'changeme',
|
||||||
|
|
|
@ -64,17 +64,17 @@ from django.utils.encoding import is_protected_type
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import ugettext
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.datastructures import SortedDict
|
|
||||||
|
|
||||||
from . import ISO_8601
|
from . import ISO_8601
|
||||||
from .settings import api_settings
|
from .settings import api_settings
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
from decimal import Decimal, DecimalException
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from decimal import Decimal, DecimalException
|
|
||||||
|
|
||||||
|
|
||||||
def is_non_str_iterable(obj):
|
def is_non_str_iterable(obj):
|
||||||
|
@ -255,7 +255,7 @@ class Field(object):
|
||||||
return [self.to_native(item) for item in value]
|
return [self.to_native(item) for item in value]
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
# Make sure we preserve field ordering, if it exists
|
# Make sure we preserve field ordering, if it exists
|
||||||
ret = SortedDict()
|
ret = OrderedDict()
|
||||||
for key, val in value.items():
|
for key, val in value.items():
|
||||||
ret[key] = self.to_native(val)
|
ret[key] = self.to_native(val)
|
||||||
return ret
|
return ret
|
||||||
|
@ -270,7 +270,7 @@ class Field(object):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def metadata(self):
|
def metadata(self):
|
||||||
metadata = SortedDict()
|
metadata = OrderedDict()
|
||||||
metadata["type"] = self.type_label
|
metadata["type"] = self.type_label
|
||||||
metadata["required"] = getattr(self, "required", False)
|
metadata["required"] = getattr(self, "required", False)
|
||||||
optional_attrs = ["read_only", "label", "help_text",
|
optional_attrs = ["read_only", "label", "help_text",
|
||||||
|
|
|
@ -59,11 +59,11 @@ from django.core.paginator import Page
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.datastructures import SortedDict
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from .settings import api_settings
|
from .settings import api_settings
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -148,7 +148,7 @@ class DictWithMetadata(dict):
|
||||||
return dict(self)
|
return dict(self)
|
||||||
|
|
||||||
|
|
||||||
class SortedDictWithMetadata(SortedDict):
|
class OrderedDictWithMetadata(OrderedDict):
|
||||||
"""
|
"""
|
||||||
A sorted dict-like object, that can have additional properties attached.
|
A sorted dict-like object, that can have additional properties attached.
|
||||||
"""
|
"""
|
||||||
|
@ -158,7 +158,7 @@ class SortedDictWithMetadata(SortedDict):
|
||||||
Overriden to remove the metadata from the dict, since it shouldn't be
|
Overriden to remove the metadata from the dict, since it shouldn't be
|
||||||
pickle and may in some instances be unpickleable.
|
pickle and may in some instances be unpickleable.
|
||||||
"""
|
"""
|
||||||
return SortedDict(self).__dict__
|
return OrderedDict(self).__dict__
|
||||||
|
|
||||||
|
|
||||||
def _is_protected_type(obj):
|
def _is_protected_type(obj):
|
||||||
|
@ -194,7 +194,7 @@ def _get_declared_fields(bases, attrs):
|
||||||
if hasattr(base, "base_fields"):
|
if hasattr(base, "base_fields"):
|
||||||
fields = list(base.base_fields.items()) + fields
|
fields = list(base.base_fields.items()) + fields
|
||||||
|
|
||||||
return SortedDict(fields)
|
return OrderedDict(fields)
|
||||||
|
|
||||||
|
|
||||||
class SerializerMetaclass(type):
|
class SerializerMetaclass(type):
|
||||||
|
@ -222,7 +222,7 @@ class BaseSerializer(WritableField):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
_options_class = SerializerOptions
|
_options_class = SerializerOptions
|
||||||
_dict_class = SortedDictWithMetadata
|
_dict_class = OrderedDictWithMetadata
|
||||||
|
|
||||||
def __init__(self, instance=None, data=None, files=None,
|
def __init__(self, instance=None, data=None, files=None,
|
||||||
context=None, partial=False, many=None,
|
context=None, partial=False, many=None,
|
||||||
|
@ -268,7 +268,7 @@ class BaseSerializer(WritableField):
|
||||||
This will be the set of any explicitly declared fields,
|
This will be the set of any explicitly declared fields,
|
||||||
plus the set of fields returned by get_default_fields().
|
plus the set of fields returned by get_default_fields().
|
||||||
"""
|
"""
|
||||||
ret = SortedDict()
|
ret = OrderedDict()
|
||||||
|
|
||||||
# Get the explicitly declared fields
|
# Get the explicitly declared fields
|
||||||
base_fields = copy.deepcopy(self.base_fields)
|
base_fields = copy.deepcopy(self.base_fields)
|
||||||
|
@ -284,7 +284,7 @@ class BaseSerializer(WritableField):
|
||||||
# If "fields" is specified, use those fields, in that order.
|
# If "fields" is specified, use those fields, in that order.
|
||||||
if self.opts.fields:
|
if self.opts.fields:
|
||||||
assert isinstance(self.opts.fields, (list, tuple)), "`fields` must be a list or tuple"
|
assert isinstance(self.opts.fields, (list, tuple)), "`fields` must be a list or tuple"
|
||||||
new = SortedDict()
|
new = OrderedDict()
|
||||||
for key in self.opts.fields:
|
for key in self.opts.fields:
|
||||||
new[key] = ret[key]
|
new[key] = ret[key]
|
||||||
ret = new
|
ret = new
|
||||||
|
@ -458,7 +458,10 @@ class BaseSerializer(WritableField):
|
||||||
many = hasattr(value, "__iter__") and not isinstance(value, (Page, dict, six.text_type))
|
many = hasattr(value, "__iter__") and not isinstance(value, (Page, dict, six.text_type))
|
||||||
|
|
||||||
if many:
|
if many:
|
||||||
|
try:
|
||||||
return [self.to_native(item) for item in value]
|
return [self.to_native(item) for item in value]
|
||||||
|
except TypeError:
|
||||||
|
pass # LazyObject is iterable so we need to catch this
|
||||||
return self.to_native(value)
|
return self.to_native(value)
|
||||||
|
|
||||||
def field_from_native(self, data, files, field_name, into):
|
def field_from_native(self, data, files, field_name, into):
|
||||||
|
@ -610,7 +613,10 @@ class BaseSerializer(WritableField):
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
|
|
||||||
if many:
|
if many:
|
||||||
|
try:
|
||||||
self._data = [self.to_native(item) for item in obj]
|
self._data = [self.to_native(item) for item in obj]
|
||||||
|
except TypeError:
|
||||||
|
self._data = self.to_native(obj) # LazyObject is iterable so we need to catch this
|
||||||
else:
|
else:
|
||||||
self._data = self.to_native(obj)
|
self._data = self.to_native(obj)
|
||||||
|
|
||||||
|
@ -645,7 +651,7 @@ class BaseSerializer(WritableField):
|
||||||
Useful for things like responding to OPTIONS requests, or generating
|
Useful for things like responding to OPTIONS requests, or generating
|
||||||
API schemas for auto-documentation.
|
API schemas for auto-documentation.
|
||||||
"""
|
"""
|
||||||
return SortedDict(
|
return OrderedDict(
|
||||||
[(field_name, field.metadata())
|
[(field_name, field.metadata())
|
||||||
for field_name, field in six.iteritems(self.fields)]
|
for field_name, field in six.iteritems(self.fields)]
|
||||||
)
|
)
|
||||||
|
@ -740,7 +746,7 @@ class ModelSerializer((six.with_metaclass(SerializerMetaclass, BaseSerializer)))
|
||||||
assert cls is not None, \
|
assert cls is not None, \
|
||||||
"Serializer class '%s' is missing `model` Meta option" % self.__class__.__name__
|
"Serializer class '%s' is missing `model` Meta option" % self.__class__.__name__
|
||||||
opts = cls._meta.concrete_model._meta
|
opts = cls._meta.concrete_model._meta
|
||||||
ret = SortedDict()
|
ret = OrderedDict()
|
||||||
nested = bool(self.opts.depth)
|
nested = bool(self.opts.depth)
|
||||||
|
|
||||||
# Deal with adding the primary key field
|
# Deal with adding the primary key field
|
||||||
|
|
|
@ -62,9 +62,10 @@ back to the defaults.
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import importlib
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
|
||||||
from . import ISO_8601
|
from . import ISO_8601
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -45,13 +45,10 @@
|
||||||
Helper classes for parsers.
|
Helper classes for parsers.
|
||||||
"""
|
"""
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.utils.datastructures import SortedDict
|
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
from taiga.base.api.serializers import DictWithMetadata, SortedDictWithMetadata
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import types
|
import types
|
||||||
|
|
|
@ -43,6 +43,8 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
|
@ -50,7 +52,6 @@ from django.http.response import HttpResponseBase
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.defaults import server_error
|
from django.views.defaults import server_error
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.utils.datastructures import SortedDict
|
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
@ -462,7 +463,7 @@ class APIView(View):
|
||||||
# By default we can't provide any form-like information, however the
|
# By default we can't provide any form-like information, however the
|
||||||
# generic views override this implementation and add additional
|
# generic views override this implementation and add additional
|
||||||
# information for POST and PUT methods, based on the serializer.
|
# information for POST and PUT methods, based on the serializer.
|
||||||
ret = SortedDict()
|
ret = OrderedDict()
|
||||||
ret['name'] = self.get_view_name()
|
ret['name'] = self.get_view_name()
|
||||||
ret['description'] = self.get_view_description()
|
ret['description'] = self.get_view_description()
|
||||||
ret['renders'] = [renderer.media_type for renderer in self.renderer_classes]
|
ret['renders'] = [renderer.media_type for renderer in self.renderer_classes]
|
||||||
|
|
|
@ -17,12 +17,14 @@
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
from .signals.thumbnails import connect_thumbnail_signals
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAppConfig(AppConfig):
|
class BaseAppConfig(AppConfig):
|
||||||
name = "taiga.base"
|
name = "taiga.base"
|
||||||
verbose_name = "Base App Config"
|
verbose_name = "Base App Config"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
from .signals.thumbnails import connect_thumbnail_signals
|
||||||
|
from .signals.cleanup_files import connect_cleanup_files_signals
|
||||||
|
|
||||||
connect_thumbnail_signals()
|
connect_thumbnail_signals()
|
||||||
|
connect_cleanup_files_signals()
|
||||||
|
|
|
@ -19,7 +19,7 @@ import datetime
|
||||||
|
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ class Command(BaseCommand):
|
||||||
}
|
}
|
||||||
|
|
||||||
for notification_email in notification_emails:
|
for notification_email in notification_emails:
|
||||||
model = get_model(*notification_email[0].split("."))
|
model = apps.get_model(*notification_email[0].split("."))
|
||||||
snapshot = {
|
snapshot = {
|
||||||
"subject": "Tests subject",
|
"subject": "Tests subject",
|
||||||
"ref": 123123,
|
"ref": 123123,
|
||||||
|
|
|
@ -43,9 +43,10 @@
|
||||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
"""The various HTTP responses for use in returning proper HTTP codes."""
|
"""The various HTTP responses for use in returning proper HTTP codes."""
|
||||||
|
from http.client import responses
|
||||||
|
|
||||||
from django import http
|
from django import http
|
||||||
|
|
||||||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
|
||||||
from django.template.response import SimpleTemplateResponse
|
from django.template.response import SimpleTemplateResponse
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
@ -114,7 +115,7 @@ class Response(SimpleTemplateResponse):
|
||||||
"""
|
"""
|
||||||
# TODO: Deprecate and use a template tag instead
|
# TODO: Deprecate and use a template tag instead
|
||||||
# TODO: Status code text for RFC 6585 status codes
|
# TODO: Status code text for RFC 6585 status codes
|
||||||
return STATUS_CODE_TEXT.get(self.status_code, '')
|
return responses.get(self.status_code, '')
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
|
||||||
|
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
||||||
|
# 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.db import models, connection
|
||||||
|
from django.db.utils import DEFAULT_DB_ALIAS, ConnectionHandler
|
||||||
|
from django.db.models.signals import pre_save, post_delete
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
from django.dispatch import Signal
|
||||||
|
|
||||||
|
cleanup_pre_delete = Signal(providing_args=["file"])
|
||||||
|
cleanup_post_delete = Signal(providing_args=["file"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _find_models_with_filefield():
|
||||||
|
result = []
|
||||||
|
for model in apps.get_models():
|
||||||
|
for field in model._meta.fields:
|
||||||
|
if isinstance(field, models.FileField):
|
||||||
|
result.append(model)
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_file(file_obj):
|
||||||
|
def delete_from_storage():
|
||||||
|
try:
|
||||||
|
cleanup_pre_delete.send(sender=None, file=file_obj)
|
||||||
|
storage.delete(file_obj.name)
|
||||||
|
cleanup_post_delete.send(sender=None, file=file_obj)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Unexpected exception while attempting "
|
||||||
|
"to delete old file '%s'".format(file_obj.name))
|
||||||
|
|
||||||
|
storage = file_obj.storage
|
||||||
|
if storage and storage.exists(file_obj.name):
|
||||||
|
connection.on_commit(delete_from_storage)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_file_fields(instance):
|
||||||
|
return filter(
|
||||||
|
lambda field: isinstance(field, models.FileField),
|
||||||
|
instance._meta.fields,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_files_on_change(sender, instance, **kwargs):
|
||||||
|
if not instance.pk:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
old_instance = sender.objects.get(pk=instance.pk)
|
||||||
|
except instance.DoesNotExist:
|
||||||
|
return
|
||||||
|
|
||||||
|
for field in _get_file_fields(instance):
|
||||||
|
old_file = getattr(old_instance, field.name)
|
||||||
|
new_file = getattr(instance, field.name)
|
||||||
|
|
||||||
|
if old_file and old_file != new_file:
|
||||||
|
_delete_file(old_file)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_files_on_delete(sender, instance, **kwargs):
|
||||||
|
for field in _get_file_fields(instance):
|
||||||
|
file_to_delete = getattr(instance, field.name)
|
||||||
|
|
||||||
|
if file_to_delete:
|
||||||
|
_delete_file(file_to_delete)
|
||||||
|
|
||||||
|
|
||||||
|
def connect_cleanup_files_signals():
|
||||||
|
connections = ConnectionHandler()
|
||||||
|
backend = connections[DEFAULT_DB_ALIAS]
|
||||||
|
|
||||||
|
for model in _find_models_with_filefield():
|
||||||
|
pre_save.connect(remove_files_on_change, sender=model)
|
||||||
|
post_delete.connect(remove_files_on_delete, sender=model)
|
|
@ -15,7 +15,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django_transactional_cleanup.signals import cleanup_post_delete
|
from .cleanup_files import cleanup_post_delete
|
||||||
from easy_thumbnails.files import get_thumbnailer
|
from easy_thumbnails.files import get_thumbnailer
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,15 +19,16 @@ import sys
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
|
||||||
from . import signal_handlers as handlers
|
|
||||||
|
|
||||||
|
|
||||||
def connect_events_signals():
|
def connect_events_signals():
|
||||||
|
from . import signal_handlers as handlers
|
||||||
signals.post_save.connect(handlers.on_save_any_model, dispatch_uid="events_change")
|
signals.post_save.connect(handlers.on_save_any_model, dispatch_uid="events_change")
|
||||||
signals.post_delete.connect(handlers.on_delete_any_model, dispatch_uid="events_delete")
|
signals.post_delete.connect(handlers.on_delete_any_model, dispatch_uid="events_delete")
|
||||||
|
|
||||||
|
|
||||||
def disconnect_events_signals():
|
def disconnect_events_signals():
|
||||||
|
from . import signal_handlers as handlers
|
||||||
signals.post_save.disconnect(dispatch_uid="events_change")
|
signals.post_save.disconnect(dispatch_uid="events_change")
|
||||||
signals.post_delete.disconnect(dispatch_uid="events_delete")
|
signals.post_delete.disconnect(dispatch_uid="events_delete")
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,6 @@ from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
from .routers import router
|
|
||||||
|
|
||||||
|
|
||||||
class FeedbackAppConfig(AppConfig):
|
class FeedbackAppConfig(AppConfig):
|
||||||
name = "taiga.feedback"
|
name = "taiga.feedback"
|
||||||
|
@ -30,4 +28,5 @@ class FeedbackAppConfig(AppConfig):
|
||||||
def ready(self):
|
def ready(self):
|
||||||
if settings.FEEDBACK_ENABLED:
|
if settings.FEEDBACK_ENABLED:
|
||||||
from taiga.urls import urlpatterns
|
from taiga.urls import urlpatterns
|
||||||
|
from .routers import router
|
||||||
urlpatterns.append(url(r'^api/v1/', include(router.urls)))
|
urlpatterns.append(url(r'^api/v1/', include(router.urls)))
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
|
|
||||||
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
|
|
||||||
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
|
|
||||||
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
|
||||||
# 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 .service import *
|
|
|
@ -144,4 +144,5 @@ def get_diff_of_htmls(html1, html2):
|
||||||
diffutil.diff_cleanupSemantic(diffs)
|
diffutil.diff_cleanupSemantic(diffs)
|
||||||
return diffutil.diff_pretty_html(diffs)
|
return diffutil.diff_pretty_html(diffs)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["render", "get_diff_of_htmls", "render_and_extract"]
|
__all__ = ["render", "get_diff_of_htmls", "render_and_extract"]
|
||||||
|
|
|
@ -19,12 +19,11 @@ from django.apps import AppConfig
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
|
||||||
from . import signals as handlers
|
|
||||||
|
|
||||||
|
|
||||||
## Project Signals
|
## Project Signals
|
||||||
|
|
||||||
def connect_projects_signals():
|
def connect_projects_signals():
|
||||||
|
from . import signals as handlers
|
||||||
# On project object is created apply template.
|
# On project object is created apply template.
|
||||||
signals.post_save.connect(handlers.project_post_save,
|
signals.post_save.connect(handlers.project_post_save,
|
||||||
sender=apps.get_model("projects", "Project"),
|
sender=apps.get_model("projects", "Project"),
|
||||||
|
@ -51,6 +50,7 @@ def disconnect_projects_signals():
|
||||||
## Memberships Signals
|
## Memberships Signals
|
||||||
|
|
||||||
def connect_memberships_signals():
|
def connect_memberships_signals():
|
||||||
|
from . import signals as handlers
|
||||||
# On membership object is deleted, update role-points relation.
|
# On membership object is deleted, update role-points relation.
|
||||||
signals.pre_delete.connect(handlers.membership_post_delete,
|
signals.pre_delete.connect(handlers.membership_post_delete,
|
||||||
sender=apps.get_model("projects", "Membership"),
|
sender=apps.get_model("projects", "Membership"),
|
||||||
|
@ -71,6 +71,7 @@ def disconnect_memberships_signals():
|
||||||
## US Statuses Signals
|
## US Statuses Signals
|
||||||
|
|
||||||
def connect_us_status_signals():
|
def connect_us_status_signals():
|
||||||
|
from . import signals as handlers
|
||||||
signals.post_save.connect(handlers.try_to_close_or_open_user_stories_when_edit_us_status,
|
signals.post_save.connect(handlers.try_to_close_or_open_user_stories_when_edit_us_status,
|
||||||
sender=apps.get_model("projects", "UserStoryStatus"),
|
sender=apps.get_model("projects", "UserStoryStatus"),
|
||||||
dispatch_uid="try_to_close_or_open_user_stories_when_edit_us_status")
|
dispatch_uid="try_to_close_or_open_user_stories_when_edit_us_status")
|
||||||
|
@ -85,6 +86,7 @@ def disconnect_us_status_signals():
|
||||||
## Tasks Statuses Signals
|
## Tasks Statuses Signals
|
||||||
|
|
||||||
def connect_task_status_signals():
|
def connect_task_status_signals():
|
||||||
|
from . import signals as handlers
|
||||||
signals.post_save.connect(handlers.try_to_close_or_open_user_stories_when_edit_task_status,
|
signals.post_save.connect(handlers.try_to_close_or_open_user_stories_when_edit_task_status,
|
||||||
sender=apps.get_model("projects", "TaskStatus"),
|
sender=apps.get_model("projects", "TaskStatus"),
|
||||||
dispatch_uid="try_to_close_or_open_user_stories_when_edit_task_status")
|
dispatch_uid="try_to_close_or_open_user_stories_when_edit_task_status")
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ class AttachmentAdmin(admin.ModelAdmin):
|
||||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AttachmentInline(generic.GenericTabularInline):
|
class AttachmentInline(GenericTabularInline):
|
||||||
model = models.Attachment
|
model = models.Attachment
|
||||||
fields = ("attached_file", "owner")
|
fields = ("attached_file", "owner")
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
|
@ -24,7 +24,7 @@ from unidecode import unidecode
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -57,7 +57,7 @@ class Attachment(models.Model):
|
||||||
verbose_name=_("content type"))
|
verbose_name=_("content type"))
|
||||||
object_id = models.PositiveIntegerField(null=False, blank=False,
|
object_id = models.PositiveIntegerField(null=False, blank=False,
|
||||||
verbose_name=_("object id"))
|
verbose_name=_("object id"))
|
||||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
content_object = GenericForeignKey("content_type", "object_id")
|
||||||
created_date = models.DateTimeField(null=False, blank=False,
|
created_date = models.DateTimeField(null=False, blank=False,
|
||||||
verbose_name=_("created date"),
|
verbose_name=_("created date"),
|
||||||
default=timezone.now)
|
default=timezone.now)
|
||||||
|
|
|
@ -19,12 +19,11 @@ from django.apps import AppConfig
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
|
||||||
from taiga.projects import signals as generic_handlers
|
|
||||||
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
|
||||||
from . import signals as handlers
|
|
||||||
|
|
||||||
|
|
||||||
def connect_issues_signals():
|
def connect_issues_signals():
|
||||||
|
from taiga.projects import signals as generic_handlers
|
||||||
|
from . import signals as handlers
|
||||||
|
|
||||||
# Finished date
|
# Finished date
|
||||||
signals.pre_save.connect(handlers.set_finished_date_when_edit_issue,
|
signals.pre_save.connect(handlers.set_finished_date_when_edit_issue,
|
||||||
sender=apps.get_model("issues", "Issue"),
|
sender=apps.get_model("issues", "Issue"),
|
||||||
|
@ -43,6 +42,8 @@ def connect_issues_signals():
|
||||||
|
|
||||||
|
|
||||||
def connect_issues_custom_attributes_signals():
|
def connect_issues_custom_attributes_signals():
|
||||||
|
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
||||||
|
|
||||||
signals.post_save.connect(custom_attributes_handlers.create_custom_attribute_value_when_create_issue,
|
signals.post_save.connect(custom_attributes_handlers.create_custom_attribute_value_when_create_issue,
|
||||||
sender=apps.get_model("issues", "Issue"),
|
sender=apps.get_model("issues", "Issue"),
|
||||||
dispatch_uid="create_custom_attribute_value_when_create_issue")
|
dispatch_uid="create_custom_attribute_value_when_create_issue")
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
@ -63,7 +63,7 @@ class Issue(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.
|
||||||
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
||||||
default=None, related_name="issues_assigned_to_me",
|
default=None, related_name="issues_assigned_to_me",
|
||||||
verbose_name=_("assigned to"))
|
verbose_name=_("assigned to"))
|
||||||
attachments = generic.GenericRelation("attachments.Attachment")
|
attachments = GenericRelation("attachments.Attachment")
|
||||||
external_reference = TextArrayField(default=None, verbose_name=_("external reference"))
|
external_reference = TextArrayField(default=None, verbose_name=_("external reference"))
|
||||||
_importing = None
|
_importing = None
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
class Like(models.Model):
|
class Like(models.Model):
|
||||||
content_type = models.ForeignKey("contenttypes.ContentType")
|
content_type = models.ForeignKey("contenttypes.ContentType")
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
content_object = GenericForeignKey("content_type", "object_id")
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||||
related_name="likes", verbose_name=_("user"))
|
related_name="likes", verbose_name=_("user"))
|
||||||
created_date = models.DateTimeField(null=False, blank=False, auto_now_add=True,
|
created_date = models.DateTimeField(null=False, blank=False, auto_now_add=True,
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -80,7 +81,7 @@ class HistoryChangeNotification(models.Model):
|
||||||
class Watched(models.Model):
|
class Watched(models.Model):
|
||||||
content_type = models.ForeignKey("contenttypes.ContentType")
|
content_type = models.ForeignKey("contenttypes.ContentType")
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
content_object = GenericForeignKey("content_type", "object_id")
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False,
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False,
|
||||||
related_name="watched", verbose_name=_("user"))
|
related_name="watched", verbose_name=_("user"))
|
||||||
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
|
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
|
||||||
|
|
|
@ -102,13 +102,13 @@ def analize_object_for_watchers(obj:object, comment:str, user:object):
|
||||||
if not hasattr(obj, "add_watcher"):
|
if not hasattr(obj, "add_watcher"):
|
||||||
return
|
return
|
||||||
|
|
||||||
from taiga import mdrender as mdr
|
|
||||||
|
|
||||||
texts = (getattr(obj, "description", ""),
|
texts = (getattr(obj, "description", ""),
|
||||||
getattr(obj, "content", ""),
|
getattr(obj, "content", ""),
|
||||||
comment,)
|
comment,)
|
||||||
|
|
||||||
_, data = mdr.render_and_extract(obj.get_project(), "\n".join(texts))
|
from taiga.mdrender.service import render_and_extract
|
||||||
|
_, data = render_and_extract(obj.get_project(), "\n".join(texts))
|
||||||
|
|
||||||
if data["mentions"]:
|
if data["mentions"]:
|
||||||
for user in data["mentions"]:
|
for user in data["mentions"]:
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.contenttypes.generic import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
|
||||||
from taiga.projects.userstories.models import UserStory
|
from taiga.projects.userstories.models import UserStory
|
||||||
from taiga.projects.tasks.models import Task
|
from taiga.projects.tasks.models import Task
|
||||||
|
|
|
@ -19,11 +19,10 @@ from django.apps import AppConfig
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
|
||||||
from taiga.projects import signals as generic_handlers
|
|
||||||
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
|
||||||
from . import signals as handlers
|
|
||||||
|
|
||||||
def connect_tasks_signals():
|
def connect_tasks_signals():
|
||||||
|
from taiga.projects import signals as generic_handlers
|
||||||
|
from . import signals as handlers
|
||||||
# Finished date
|
# Finished date
|
||||||
signals.pre_save.connect(handlers.set_finished_date_when_edit_task,
|
signals.pre_save.connect(handlers.set_finished_date_when_edit_task,
|
||||||
sender=apps.get_model("tasks", "Task"),
|
sender=apps.get_model("tasks", "Task"),
|
||||||
|
@ -40,6 +39,7 @@ def connect_tasks_signals():
|
||||||
dispatch_uid="update_project_tags_when_delete_tagglabe_item_task")
|
dispatch_uid="update_project_tags_when_delete_tagglabe_item_task")
|
||||||
|
|
||||||
def connect_tasks_close_or_open_us_and_milestone_signals():
|
def connect_tasks_close_or_open_us_and_milestone_signals():
|
||||||
|
from . import signals as handlers
|
||||||
# Cached prev object version
|
# Cached prev object version
|
||||||
signals.pre_save.connect(handlers.cached_prev_task,
|
signals.pre_save.connect(handlers.cached_prev_task,
|
||||||
sender=apps.get_model("tasks", "Task"),
|
sender=apps.get_model("tasks", "Task"),
|
||||||
|
@ -53,6 +53,7 @@ def connect_tasks_close_or_open_us_and_milestone_signals():
|
||||||
dispatch_uid="try_to_close_or_open_us_and_milestone_when_delete_task")
|
dispatch_uid="try_to_close_or_open_us_and_milestone_when_delete_task")
|
||||||
|
|
||||||
def connect_tasks_custom_attributes_signals():
|
def connect_tasks_custom_attributes_signals():
|
||||||
|
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
||||||
signals.post_save.connect(custom_attributes_handlers.create_custom_attribute_value_when_create_task,
|
signals.post_save.connect(custom_attributes_handlers.create_custom_attribute_value_when_create_task,
|
||||||
sender=apps.get_model("tasks", "Task"),
|
sender=apps.get_model("tasks", "Task"),
|
||||||
dispatch_uid="create_custom_attribute_value_when_create_task")
|
dispatch_uid="create_custom_attribute_value_when_create_task")
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -62,7 +62,7 @@ class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.M
|
||||||
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
||||||
default=None, related_name="tasks_assigned_to_me",
|
default=None, related_name="tasks_assigned_to_me",
|
||||||
verbose_name=_("assigned to"))
|
verbose_name=_("assigned to"))
|
||||||
attachments = generic.GenericRelation("attachments.Attachment")
|
attachments = GenericRelation("attachments.Attachment")
|
||||||
is_iocaine = models.BooleanField(default=False, null=False, blank=True,
|
is_iocaine = models.BooleanField(default=False, null=False, blank=True,
|
||||||
verbose_name=_("is iocaine"))
|
verbose_name=_("is iocaine"))
|
||||||
external_reference = TextArrayField(default=None, verbose_name=_("external reference"))
|
external_reference = TextArrayField(default=None, verbose_name=_("external reference"))
|
||||||
|
|
|
@ -19,12 +19,10 @@ from django.apps import AppConfig
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
|
||||||
from taiga.projects import signals as generic_handlers
|
|
||||||
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
|
||||||
from . import signals as handlers
|
|
||||||
|
|
||||||
|
|
||||||
def connect_userstories_signals():
|
def connect_userstories_signals():
|
||||||
|
from taiga.projects import signals as generic_handlers
|
||||||
|
from . import signals as handlers
|
||||||
# Cached prev object version
|
# Cached prev object version
|
||||||
signals.pre_save.connect(handlers.cached_prev_us,
|
signals.pre_save.connect(handlers.cached_prev_us,
|
||||||
sender=apps.get_model("userstories", "UserStory"),
|
sender=apps.get_model("userstories", "UserStory"),
|
||||||
|
@ -61,6 +59,7 @@ def connect_userstories_signals():
|
||||||
|
|
||||||
|
|
||||||
def connect_userstories_custom_attributes_signals():
|
def connect_userstories_custom_attributes_signals():
|
||||||
|
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
||||||
signals.post_save.connect(custom_attributes_handlers.create_custom_attribute_value_when_create_user_story,
|
signals.post_save.connect(custom_attributes_handlers.create_custom_attribute_value_when_create_user_story,
|
||||||
sender=apps.get_model("userstories", "UserStory"),
|
sender=apps.get_model("userstories", "UserStory"),
|
||||||
dispatch_uid="create_custom_attribute_value_when_create_user_story")
|
dispatch_uid="create_custom_attribute_value_when_create_user_story")
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -69,7 +69,7 @@ class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, mod
|
||||||
related_name="user_stories", verbose_name=_("status"),
|
related_name="user_stories", verbose_name=_("status"),
|
||||||
on_delete=models.SET_NULL)
|
on_delete=models.SET_NULL)
|
||||||
is_closed = models.BooleanField(default=False)
|
is_closed = models.BooleanField(default=False)
|
||||||
points = models.ManyToManyField("projects.Points", null=False, blank=False,
|
points = models.ManyToManyField("projects.Points", blank=False,
|
||||||
related_name="userstories", through="RolePoints",
|
related_name="userstories", through="RolePoints",
|
||||||
verbose_name=_("points"))
|
verbose_name=_("points"))
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, mod
|
||||||
verbose_name=_("is client requirement"))
|
verbose_name=_("is client requirement"))
|
||||||
team_requirement = models.BooleanField(default=False, null=False, blank=True,
|
team_requirement = models.BooleanField(default=False, null=False, blank=True,
|
||||||
verbose_name=_("is team requirement"))
|
verbose_name=_("is team requirement"))
|
||||||
attachments = generic.GenericRelation("attachments.Attachment")
|
attachments = GenericRelation("attachments.Attachment")
|
||||||
generated_from_issue = models.ForeignKey("issues.Issue", null=True, blank=True,
|
generated_from_issue = models.ForeignKey("issues.Issue", null=True, blank=True,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name="generated_user_stories",
|
related_name="generated_user_stories",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
class Votes(models.Model):
|
class Votes(models.Model):
|
||||||
content_type = models.ForeignKey("contenttypes.ContentType")
|
content_type = models.ForeignKey("contenttypes.ContentType")
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
content_object = GenericForeignKey("content_type", "object_id")
|
||||||
count = models.PositiveIntegerField(null=False, blank=False, default=0, verbose_name=_("count"))
|
count = models.PositiveIntegerField(null=False, blank=False, default=0, verbose_name=_("count"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -46,7 +46,7 @@ class Votes(models.Model):
|
||||||
class Vote(models.Model):
|
class Vote(models.Model):
|
||||||
content_type = models.ForeignKey("contenttypes.ContentType")
|
content_type = models.ForeignKey("contenttypes.ContentType")
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
content_object = GenericForeignKey("content_type", "object_id")
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||||
related_name="votes", verbose_name=_("user"))
|
related_name="votes", verbose_name=_("user"))
|
||||||
created_date = models.DateTimeField(null=False, blank=False, auto_now_add=True,
|
created_date = models.DateTimeField(null=False, blank=False, auto_now_add=True,
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -41,7 +41,7 @@ class WikiPage(OCCModelMixin, WatchedModelMixin, models.Model):
|
||||||
default=timezone.now)
|
default=timezone.now)
|
||||||
modified_date = models.DateTimeField(null=False, blank=False,
|
modified_date = models.DateTimeField(null=False, blank=False,
|
||||||
verbose_name=_("modified date"))
|
verbose_name=_("modified date"))
|
||||||
attachments = generic.GenericRelation("attachments.Attachment")
|
attachments = GenericRelation("attachments.Attachment")
|
||||||
_importing = None
|
_importing = None
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -19,16 +19,17 @@ from django.apps import AppConfig
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
|
||||||
from . import signals as handlers
|
|
||||||
from taiga.projects.history.models import HistoryEntry
|
|
||||||
|
|
||||||
|
|
||||||
class TimelineAppConfig(AppConfig):
|
class TimelineAppConfig(AppConfig):
|
||||||
name = "taiga.timeline"
|
name = "taiga.timeline"
|
||||||
verbose_name = "Timeline"
|
verbose_name = "Timeline"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
signals.post_save.connect(handlers.on_new_history_entry, sender=HistoryEntry, dispatch_uid="timeline")
|
from . import signals as handlers
|
||||||
|
|
||||||
|
signals.post_save.connect(handlers.on_new_history_entry,
|
||||||
|
sender=apps.get_model("history", "HistoryEntry"),
|
||||||
|
dispatch_uid="timeline")
|
||||||
signals.pre_save.connect(handlers.create_membership_push_to_timeline,
|
signals.pre_save.connect(handlers.create_membership_push_to_timeline,
|
||||||
sender=apps.get_model("projects", "Membership"))
|
sender=apps.get_model("projects", "Membership"))
|
||||||
signals.post_delete.connect(handlers.delete_membership_push_to_timeline,
|
signals.post_delete.connect(handlers.delete_membership_push_to_timeline,
|
||||||
|
|
|
@ -22,7 +22,7 @@ from django.utils import timezone
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.contenttypes.generic import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
|
||||||
from taiga.projects.models import Project
|
from taiga.projects.models import Project
|
||||||
|
|
||||||
|
|
|
@ -15,15 +15,16 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
|
|
||||||
from . import signal_handlers as handlers
|
|
||||||
from taiga.projects.history.models import HistoryEntry
|
|
||||||
|
|
||||||
|
|
||||||
def connect_webhooks_signals():
|
def connect_webhooks_signals():
|
||||||
signals.post_save.connect(handlers.on_new_history_entry, sender=HistoryEntry, dispatch_uid="webhooks")
|
from . import signal_handlers as handlers
|
||||||
|
signals.post_save.connect(handlers.on_new_history_entry,
|
||||||
|
sender=apps.get_model("history", "HistoryEntry"),
|
||||||
|
dispatch_uid="webhooks")
|
||||||
|
|
||||||
|
|
||||||
def disconnect_webhooks_signals():
|
def disconnect_webhooks_signals():
|
||||||
|
|
|
@ -10,35 +10,36 @@ pytestmark = pytest.mark.django_db(transaction=True)
|
||||||
import factory
|
import factory
|
||||||
|
|
||||||
|
|
||||||
class TestingProjectModel(models.Model):
|
class AuxProjectModel(models.Model):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class TestingModelWithNameAttribute(models.Model):
|
class AuxModelWithNameAttribute(models.Model):
|
||||||
name = models.CharField(max_length=255, null=False, blank=False)
|
name = models.CharField(max_length=255, null=False, blank=False)
|
||||||
project = models.ForeignKey(TestingProjectModel, null=False, blank=False)
|
project = models.ForeignKey(AuxProjectModel, null=False, blank=False)
|
||||||
|
|
||||||
|
|
||||||
class TestingSerializer(ValidateDuplicatedNameInProjectMixin):
|
class AuxSerializer(ValidateDuplicatedNameInProjectMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TestingModelWithNameAttribute
|
model = AuxModelWithNameAttribute
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_duplicated_name_validation():
|
def test_duplicated_name_validation():
|
||||||
project = TestingProjectModel.objects.create()
|
project = AuxProjectModel.objects.create()
|
||||||
instance_1 = TestingModelWithNameAttribute.objects.create(name="1", project=project)
|
instance_1 = AuxModelWithNameAttribute.objects.create(name="1", project=project)
|
||||||
instance_2 = TestingModelWithNameAttribute.objects.create(name="2", project=project)
|
instance_2 = AuxModelWithNameAttribute.objects.create(name="2", project=project)
|
||||||
|
|
||||||
# No duplicated_name
|
# No duplicated_name
|
||||||
serializer = TestingSerializer(data={"name": "3", "project": project.id})
|
serializer = AuxSerializer(data={"name": "3", "project": project.id})
|
||||||
|
|
||||||
assert serializer.is_valid()
|
assert serializer.is_valid()
|
||||||
|
|
||||||
# Create duplicated_name
|
# Create duplicated_name
|
||||||
serializer = TestingSerializer(data={"name": "1", "project": project.id})
|
serializer = AuxSerializer(data={"name": "1", "project": project.id})
|
||||||
|
|
||||||
assert not serializer.is_valid()
|
assert not serializer.is_valid()
|
||||||
|
|
||||||
# Update name to existing one
|
# Update name to existing one
|
||||||
serializer = TestingSerializer(data={"id": instance_2.id, "name": "1","project": project.id})
|
serializer = AuxSerializer(data={"id": instance_2.id, "name": "1","project": project.id})
|
||||||
|
|
||||||
assert not serializer.is_valid()
|
assert not serializer.is_valid()
|
||||||
|
|
Loading…
Reference in New Issue