commit
fb7dfd2bc4
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,5 +1,13 @@
|
||||||
<a name="1.0.0"></a>
|
# Changelog #
|
||||||
# 1.0.0 taiga-back (2014-10-07)
|
|
||||||
|
## 1.1.0 (2014-10-13)
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
- Fix bugs related to unicode chars on attachments.
|
||||||
|
- Fix wrong static url resolve usage on emails.
|
||||||
|
- Fix some bugs on import/export api related with attachments.
|
||||||
|
|
||||||
|
## 1.0.0 (2014-10-07)
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
- Lots of small and not so small bugfixes
|
- Lots of small and not so small bugfixes
|
||||||
|
|
|
@ -20,9 +20,10 @@ fn==0.2.13
|
||||||
diff-match-patch==20121119
|
diff-match-patch==20121119
|
||||||
requests==2.4.1
|
requests==2.4.1
|
||||||
|
|
||||||
# Comment it if you are using python >= 3.4
|
|
||||||
enum34==1.0
|
|
||||||
easy-thumbnails==2.1
|
easy-thumbnails==2.1
|
||||||
celery==3.1.12
|
celery==3.1.12
|
||||||
redis==2.10.3
|
redis==2.10.3
|
||||||
Unidecode==0.04.16
|
Unidecode==0.04.16
|
||||||
|
|
||||||
|
# Comment it if you are using python >= 3.4
|
||||||
|
enum34==1.0
|
||||||
|
|
|
@ -36,7 +36,7 @@ from taiga.users.serializers import UserSerializer
|
||||||
from taiga.users.services import get_and_validate_user
|
from taiga.users.services import get_and_validate_user
|
||||||
|
|
||||||
from .backends import get_token_for_user
|
from .backends import get_token_for_user
|
||||||
|
from .signals import user_registered as user_registered_signal
|
||||||
|
|
||||||
def send_public_register_email(user) -> bool:
|
def send_public_register_email(user) -> bool:
|
||||||
"""
|
"""
|
||||||
|
@ -126,6 +126,7 @@ def public_register(username:str, password:str, email:str, full_name:str):
|
||||||
raise exc.WrongArguments("User is already register.")
|
raise exc.WrongArguments("User is already register.")
|
||||||
|
|
||||||
# send_public_register_email(user)
|
# send_public_register_email(user)
|
||||||
|
user_registered_signal.send(sender=user.__class__, user=user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,6 +178,7 @@ def private_register_for_new_user(token:str, username:str, email:str,
|
||||||
membership = get_membership_by_token(token)
|
membership = get_membership_by_token(token)
|
||||||
membership.user = user
|
membership.user = user
|
||||||
membership.save(update_fields=["user"])
|
membership.save(update_fields=["user"])
|
||||||
|
user_registered_signal.send(sender=user.__class__, user=user)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
@ -202,6 +204,9 @@ def github_register(username:str, email:str, full_name:str, github_id:int, bio:s
|
||||||
membership.user = user
|
membership.user = user
|
||||||
membership.save(update_fields=["user"])
|
membership.save(update_fields=["user"])
|
||||||
|
|
||||||
|
if created:
|
||||||
|
user_registered_signal.send(sender=user.__class__, user=user)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,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_jinja import library
|
import django.dispatch
|
||||||
from django_sites.utils import static
|
|
||||||
|
|
||||||
register = library.Library()
|
|
||||||
|
|
||||||
@register.global_function
|
user_registered = django.dispatch.Signal(providing_args=["user"])
|
||||||
def full_url_static(text) -> str:
|
|
||||||
return static(text)
|
|
|
@ -95,7 +95,7 @@
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ home_url }}"
|
<a href="{{ home_url }}"
|
||||||
title="{{ home_url_name }}">
|
title="{{ home_url_name }}">
|
||||||
<img src="{{ full_url_static("emails/email-logo.png") }}" alt="Taiga" height="32" />
|
<img src="{{ static("emails/email-logo.png") }}" alt="Taiga" height="32" />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -15,7 +15,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/>.
|
||||||
|
|
||||||
|
|
||||||
def make_diff(first:dict, second:dict, not_found_value=None) -> dict:
|
def make_diff(first:dict, second:dict, not_found_value=None, excluded_keys:tuple=()) -> dict:
|
||||||
"""
|
"""
|
||||||
Compute a diff between two dicts.
|
Compute a diff between two dicts.
|
||||||
"""
|
"""
|
||||||
|
@ -39,4 +39,9 @@ def make_diff(first:dict, second:dict, not_found_value=None) -> dict:
|
||||||
if frst == scnd:
|
if frst == scnd:
|
||||||
del diff[key]
|
del diff[key]
|
||||||
|
|
||||||
|
# Removed excluded keys
|
||||||
|
for key in excluded_keys:
|
||||||
|
if key in diff:
|
||||||
|
del diff[key]
|
||||||
|
|
||||||
return diff
|
return diff
|
||||||
|
|
|
@ -15,6 +15,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/>.
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
import os.path as path
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
@ -206,6 +207,8 @@ def store_attachment(project, obj, attachment):
|
||||||
if serialized.object.owner is None:
|
if serialized.object.owner is None:
|
||||||
serialized.object.owner = serialized.object.project.owner
|
serialized.object.owner = serialized.object.project.owner
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
|
serialized.object.size = serialized.object.attached_file.size
|
||||||
|
serialized.object.name = path.basename(serialized.object.attached_file.name).lower()
|
||||||
serialized.save()
|
serialized.save()
|
||||||
return serialized
|
return serialized
|
||||||
add_errors("attachments", serialized.errors)
|
add_errors("attachments", serialized.errors)
|
||||||
|
|
|
@ -162,6 +162,7 @@ def extract_attachments(obj) -> list:
|
||||||
for attach in obj.attachments.all():
|
for attach in obj.attachments.all():
|
||||||
yield {"id": attach.id,
|
yield {"id": attach.id,
|
||||||
"filename": os.path.basename(attach.attached_file.name),
|
"filename": os.path.basename(attach.attached_file.name),
|
||||||
|
"url": attach.attached_file.url,
|
||||||
"description": attach.description,
|
"description": attach.description,
|
||||||
"is_deprecated": attach.is_deprecated,
|
"is_deprecated": attach.is_deprecated,
|
||||||
"description": attach.description,
|
"description": attach.description,
|
||||||
|
|
|
@ -166,10 +166,14 @@ class HistoryEntry(models.Model):
|
||||||
|
|
||||||
for aid in set(tuple(oldattachs.keys()) + tuple(newattachs.keys())):
|
for aid in set(tuple(oldattachs.keys()) + tuple(newattachs.keys())):
|
||||||
if aid in oldattachs and aid in newattachs:
|
if aid in oldattachs and aid in newattachs:
|
||||||
if oldattachs[aid] != newattachs[aid]:
|
changes = make_diff_from_dicts(oldattachs[aid], newattachs[aid],
|
||||||
|
excluded_keys=("filename", "url"))
|
||||||
|
|
||||||
|
if changes:
|
||||||
change = {
|
change = {
|
||||||
"filename": oldattachs[aid]["filename"],
|
"filename": newattachs[aid]["filename"],
|
||||||
"changes": make_diff_from_dicts(oldattachs[aid], newattachs[aid])
|
"url": newattachs[aid]["url"],
|
||||||
|
"changes": changes
|
||||||
}
|
}
|
||||||
attachments["changed"].append(change)
|
attachments["changed"].append(change)
|
||||||
elif aid in oldattachs and aid not in newattachs:
|
elif aid in oldattachs and aid not in newattachs:
|
||||||
|
|
|
@ -37,9 +37,10 @@
|
||||||
|
|
||||||
{% for att in values['new']%}
|
{% for att in values['new']%}
|
||||||
<dd style="background: #eee; padding: 5px 15px; color: #444">
|
<dd style="background: #eee; padding: 5px 15px; color: #444">
|
||||||
<i>
|
<a href="{{ att.url }}" target="_blank" style="font-weight: bold; color: #444">
|
||||||
{{ att.filename|linebreaksbr }} {% if att.description %}({{ att.description|linebreaksbr }}){% endif %}
|
{{ att.filename|linebreaksbr }}
|
||||||
</i>
|
</a>
|
||||||
|
{% if att.description %}<i> {{ att.description|linebreaksbr }}</i>{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -51,7 +52,9 @@
|
||||||
|
|
||||||
{% for att in values['changed'] %}
|
{% for att in values['changed'] %}
|
||||||
<dd style="background: #eee; padding: 5px 15px; color: #444">
|
<dd style="background: #eee; padding: 5px 15px; color: #444">
|
||||||
<b>{{ att.filename|linebreaksbr }}</b>
|
<a href="{{ att.url }}" target="_blank" style="font-weight: bold; color: #444">
|
||||||
|
{{ att.filename|linebreaksbr }}
|
||||||
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
{% if att.changes.is_deprecated %}
|
{% if att.changes.is_deprecated %}
|
||||||
{% if att.changes.is_deprecated.1 %}
|
{% if att.changes.is_deprecated.1 %}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('userstories', '0004_auto_20141001_1817'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='userstory',
|
||||||
|
name='generated_from_issue',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='issues.Issue', verbose_name='generated from issue', related_name='generated_user_stories', null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -94,6 +94,7 @@ class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, mod
|
||||||
verbose_name=_("is team requirement"))
|
verbose_name=_("is team requirement"))
|
||||||
attachments = generic.GenericRelation("attachments.Attachment")
|
attachments = generic.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,
|
||||||
related_name="generated_user_stories",
|
related_name="generated_user_stories",
|
||||||
verbose_name=_("generated from issue"))
|
verbose_name=_("generated from issue"))
|
||||||
_importing = None
|
_importing = None
|
||||||
|
|
|
@ -50,7 +50,7 @@ class MembersFilterBackend(BaseFilterBackend):
|
||||||
if project_id:
|
if project_id:
|
||||||
Project = apps.get_model('projects', 'Project')
|
Project = apps.get_model('projects', 'Project')
|
||||||
project = get_object_or_404(Project, pk=project_id)
|
project = get_object_or_404(Project, pk=project_id)
|
||||||
if project.memberships.filter(user=request.user).exists() or project.owner == request.user:
|
if request.user.is_authenticated() and (project.memberships.filter(user=request.user).exists() or project.owner == request.user):
|
||||||
return queryset.filter(Q(memberships__project=project) | Q(id=project.owner.id)).distinct()
|
return queryset.filter(Q(memberships__project=project) | Q(id=project.owner.id)).distinct()
|
||||||
else:
|
else:
|
||||||
raise exc.PermissionDenied(_("You don't have permisions to see this project users."))
|
raise exc.PermissionDenied(_("You don't have permisions to see this project users."))
|
||||||
|
|
|
@ -20,6 +20,8 @@ import os.path as path
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from unidecode import unidecode
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -27,6 +29,7 @@ from django.contrib.auth.models import UserManager, AbstractBaseUser
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
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.template.defaultfilters import slugify
|
||||||
|
|
||||||
from djorm_pgarray.fields import TextArrayField
|
from djorm_pgarray.fields import TextArrayField
|
||||||
|
|
||||||
|
@ -41,6 +44,9 @@ def generate_random_hex_color():
|
||||||
|
|
||||||
def get_user_file_path(instance, filename):
|
def get_user_file_path(instance, filename):
|
||||||
basename = path.basename(filename).lower()
|
basename = path.basename(filename).lower()
|
||||||
|
base, ext = path.splitext(basename)
|
||||||
|
base = slugify(unidecode(base))
|
||||||
|
basename = "".join([base, ext])
|
||||||
|
|
||||||
hs = hashlib.sha256()
|
hs = hashlib.sha256()
|
||||||
hs.update(force_bytes(timezone.now().isoformat()))
|
hs.update(force_bytes(timezone.now().isoformat()))
|
||||||
|
|
Loading…
Reference in New Issue