commit
57777fc54c
|
@ -1,6 +1,6 @@
|
|||
# Changelog #
|
||||
|
||||
## 1.3.0 Dryas hookeriana (Unreleased)
|
||||
## 1.3.0 Dryas hookeriana (2014-11-18)
|
||||
|
||||
### Features
|
||||
- GitHub integration (Phase I):
|
||||
|
|
|
@ -1 +1,17 @@
|
|||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 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 . import celery
|
||||
|
|
|
@ -34,6 +34,7 @@ from djmail.template_mail import MagicMailBuilder
|
|||
from taiga.base import exceptions as exc
|
||||
from taiga.users.serializers import UserSerializer
|
||||
from taiga.users.services import get_and_validate_user
|
||||
from taiga.base.utils.slug import slugify_uniquely
|
||||
|
||||
from .tokens import get_token_for_user
|
||||
from .signals import user_registered as user_registered_signal
|
||||
|
@ -50,7 +51,7 @@ def send_register_email(user) -> bool:
|
|||
return bool(email.send())
|
||||
|
||||
|
||||
def is_user_already_registered(*, username:str, email:str, github_id:int=None) -> (bool, str):
|
||||
def is_user_already_registered(*, username:str, email:str) -> (bool, str):
|
||||
"""
|
||||
Checks if a specified user is already registred.
|
||||
|
||||
|
@ -65,9 +66,6 @@ def is_user_already_registered(*, username:str, email:str, github_id:int=None) -
|
|||
if user_model.objects.filter(email=email):
|
||||
return (True, _("Email is already in use."))
|
||||
|
||||
if github_id and user_model.objects.filter(github_id=github_id):
|
||||
return (True, _("GitHub id is already in use"))
|
||||
|
||||
return (False, None)
|
||||
|
||||
|
||||
|
@ -182,20 +180,33 @@ def github_register(username:str, email:str, full_name:str, github_id:int, bio:s
|
|||
:returns: User
|
||||
"""
|
||||
user_model = apps.get_model("users", "User")
|
||||
user, created = user_model.objects.get_or_create(github_id=github_id,
|
||||
defaults={"username": username,
|
||||
"email": email,
|
||||
"full_name": full_name,
|
||||
"bio": bio})
|
||||
|
||||
try:
|
||||
# Github user association exist?
|
||||
user = user_model.objects.get(github_id=github_id)
|
||||
except user_model.DoesNotExist:
|
||||
try:
|
||||
# Is a user with the same email as the github user?
|
||||
user = user_model.objects.get(email=email)
|
||||
user.github_id = github_id
|
||||
user.save(update_fields=["github_id"])
|
||||
except user_model.DoesNotExist:
|
||||
# Create a new user
|
||||
username_unique = slugify_uniquely(username, user_model, slugfield="username")
|
||||
user = user_model.objects.create(email=email,
|
||||
username=username_unique,
|
||||
github_id=github_id,
|
||||
full_name=full_name,
|
||||
bio=bio)
|
||||
|
||||
send_register_email(user)
|
||||
user_registered_signal.send(sender=user.__class__, user=user)
|
||||
|
||||
if token:
|
||||
membership = get_membership_by_token(token)
|
||||
membership.user = user
|
||||
membership.save(update_fields=["user"])
|
||||
|
||||
if created:
|
||||
send_register_email(user)
|
||||
user_registered_signal.send(sender=user.__class__, user=user)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 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/>.
|
||||
|
||||
import os
|
||||
|
||||
from celery import Celery
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 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 .celery import app
|
||||
|
|
|
@ -110,11 +110,11 @@ class IssuesEventHook(BaseEventHook):
|
|||
|
||||
subject = self.payload.get('issue', {}).get('title', None)
|
||||
description = self.payload.get('issue', {}).get('body', None)
|
||||
github_reference = self.payload.get('issue', {}).get('number', None)
|
||||
github_url = self.payload.get('issue', {}).get('html_url', None)
|
||||
github_user = self.payload.get('issue', {}).get('user', {}).get('id', None)
|
||||
project_url = self.payload.get('repository', {}).get('html_url', None)
|
||||
|
||||
if not all([subject, github_reference, project_url]):
|
||||
if not all([subject, github_url, project_url]):
|
||||
raise ActionSyntaxException(_("Invalid issue information"))
|
||||
|
||||
issue = Issue.objects.create(
|
||||
|
@ -125,7 +125,7 @@ class IssuesEventHook(BaseEventHook):
|
|||
type=self.project.default_issue_type,
|
||||
severity=self.project.default_severity,
|
||||
priority=self.project.default_priority,
|
||||
external_reference=['github', github_reference],
|
||||
external_reference=['github', github_url],
|
||||
owner=get_github_user(github_user)
|
||||
)
|
||||
take_snapshot(issue, user=get_github_user(github_user))
|
||||
|
@ -139,18 +139,18 @@ class IssueCommentEventHook(BaseEventHook):
|
|||
if self.payload.get('action', None) != "created":
|
||||
raise ActionSyntaxException(_("Invalid issue comment information"))
|
||||
|
||||
github_reference = self.payload.get('issue', {}).get('number', None)
|
||||
github_url = self.payload.get('issue', {}).get('html_url', None)
|
||||
comment_message = self.payload.get('comment', {}).get('body', None)
|
||||
github_user = self.payload.get('sender', {}).get('id', None)
|
||||
project_url = self.payload.get('repository', {}).get('html_url', None)
|
||||
comment_message = replace_github_references(project_url, comment_message)
|
||||
|
||||
if not all([comment_message, github_reference, project_url]):
|
||||
if not all([comment_message, github_url, project_url]):
|
||||
raise ActionSyntaxException(_("Invalid issue comment information"))
|
||||
|
||||
issues = Issue.objects.filter(external_reference=["github", github_reference])
|
||||
tasks = Task.objects.filter(external_reference=["github", github_reference])
|
||||
uss = UserStory.objects.filter(external_reference=["github", github_reference])
|
||||
issues = Issue.objects.filter(external_reference=["github", github_url])
|
||||
tasks = Task.objects.filter(external_reference=["github", github_url])
|
||||
uss = UserStory.objects.filter(external_reference=["github", github_url])
|
||||
|
||||
for item in list(issues) + list(tasks) + list(uss):
|
||||
snapshot = take_snapshot(item,
|
||||
|
|
|
@ -52,7 +52,7 @@ from .extensions.references import TaigaReferencesExtension
|
|||
|
||||
|
||||
# Bleach configuration
|
||||
bleach.ALLOWED_TAGS += ["p", "table", "th", "tr", "td", "h1", "h2", "h3",
|
||||
bleach.ALLOWED_TAGS += ["p", "table", "thead", "tbody", "th", "tr", "td", "h1", "h2", "h3",
|
||||
"div", "pre", "span", "hr", "dl", "dt", "dd", "sup",
|
||||
"img", "del", "br", "ins"]
|
||||
|
||||
|
@ -74,7 +74,8 @@ def _make_extensions_list(wikilinks_config=None, project=None):
|
|||
MentionsExtension(),
|
||||
TaigaReferencesExtension(project),
|
||||
"extra",
|
||||
"codehilite"]
|
||||
"codehilite",
|
||||
"nl2br"]
|
||||
|
||||
|
||||
import diff_match_patch
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from taiga.base.serializers import Serializer, PickleField, NeighborsSerializerMixin
|
||||
from taiga.base.serializers import Serializer, PickleField, NeighborsSerializerMixin, PgArrayField
|
||||
from taiga.mdrender.service import render as mdrender
|
||||
from taiga.projects.validators import ProjectExistsValidator
|
||||
from taiga.projects.notifications.validators import WatchersValidator
|
||||
|
@ -26,6 +26,7 @@ from . import models
|
|||
|
||||
class IssueSerializer(WatchersValidator, serializers.ModelSerializer):
|
||||
tags = PickleField(required=False)
|
||||
external_reference = PgArrayField(required=False)
|
||||
is_closed = serializers.Field(source="is_closed")
|
||||
comment = serializers.SerializerMethodField("get_comment")
|
||||
generated_user_stories = serializers.SerializerMethodField("get_generated_user_stories")
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from taiga.base.serializers import Serializer, PickleField, NeighborsSerializerMixin
|
||||
from taiga.base.serializers import Serializer, PickleField, NeighborsSerializerMixin, PgArrayField
|
||||
from taiga.mdrender.service import render as mdrender
|
||||
from taiga.projects.validators import ProjectExistsValidator, TaskStatusExistsValidator
|
||||
from taiga.projects.milestones.validators import SprintExistsValidator
|
||||
|
@ -28,6 +28,7 @@ from . import models
|
|||
|
||||
class TaskSerializer(WatchersValidator, serializers.ModelSerializer):
|
||||
tags = PickleField(required=False, default=[])
|
||||
external_reference = PgArrayField(required=False)
|
||||
comment = serializers.SerializerMethodField("get_comment")
|
||||
milestone_slug = serializers.SerializerMethodField("get_milestone_slug")
|
||||
blocked_note_html = serializers.SerializerMethodField("get_blocked_note_html")
|
||||
|
|
|
@ -126,9 +126,15 @@ class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, mod
|
|||
return self.role_points
|
||||
|
||||
def get_total_points(self):
|
||||
not_null_role_points = self.role_points.select_related("points").\
|
||||
exclude(points__value__isnull=True)
|
||||
|
||||
#If we only have None values the sum should be None
|
||||
if not not_null_role_points:
|
||||
return None
|
||||
|
||||
total = 0.0
|
||||
for rp in self.role_points.select_related("points"):
|
||||
if rp.points.value:
|
||||
total += rp.points.value
|
||||
for rp in not_null_role_points:
|
||||
total += rp.points.value
|
||||
|
||||
return total
|
||||
|
|
|
@ -18,7 +18,7 @@ import json
|
|||
from django.apps import apps
|
||||
from rest_framework import serializers
|
||||
|
||||
from taiga.base.serializers import Serializer, PickleField, NeighborsSerializerMixin
|
||||
from taiga.base.serializers import Serializer, PickleField, NeighborsSerializerMixin, PgArrayField
|
||||
from taiga.mdrender.service import render as mdrender
|
||||
from taiga.projects.validators import ProjectExistsValidator, UserStoryStatusExistsValidator
|
||||
from taiga.projects.userstories.validators import UserStoryExistsValidator
|
||||
|
@ -39,6 +39,7 @@ class RolePointsField(serializers.WritableField):
|
|||
|
||||
class UserStorySerializer(WatchersValidator, serializers.ModelSerializer):
|
||||
tags = PickleField(default=[], required=False)
|
||||
external_reference = PgArrayField(required=False)
|
||||
points = RolePointsField(source="role_points", required=False)
|
||||
total_points = serializers.SerializerMethodField("get_total_points")
|
||||
comment = serializers.SerializerMethodField("get_comment")
|
||||
|
|
|
@ -115,6 +115,78 @@ def test_response_200_in_registration_with_github_account(client, settings):
|
|||
assert response.data["bio"] == "time traveler"
|
||||
assert response.data["github_id"] == 1955
|
||||
|
||||
def test_response_200_in_registration_with_github_account_and_existed_user_by_email(client, settings):
|
||||
settings.PUBLIC_REGISTER_ENABLED = False
|
||||
form = {"type": "github",
|
||||
"code": "xxxxxx"}
|
||||
user = factories.UserFactory()
|
||||
user.email = "mmcfly@bttf.com"
|
||||
user.github_id = None
|
||||
user.save()
|
||||
|
||||
with patch("taiga.base.connectors.github.me") as m_me:
|
||||
m_me.return_value = ("mmcfly@bttf.com",
|
||||
github.User(id=1955,
|
||||
username="mmcfly",
|
||||
full_name="martin seamus mcfly",
|
||||
bio="time traveler"))
|
||||
|
||||
response = client.post(reverse("auth-list"), form)
|
||||
assert response.status_code == 200
|
||||
assert response.data["username"] == user.username
|
||||
assert response.data["auth_token"] != "" and response.data["auth_token"] != None
|
||||
assert response.data["email"] == user.email
|
||||
assert response.data["full_name"] == user.full_name
|
||||
assert response.data["bio"] == user.bio
|
||||
assert response.data["github_id"] == 1955
|
||||
|
||||
def test_response_200_in_registration_with_github_account_and_existed_user_by_github_id(client, settings):
|
||||
settings.PUBLIC_REGISTER_ENABLED = False
|
||||
form = {"type": "github",
|
||||
"code": "xxxxxx"}
|
||||
user = factories.UserFactory()
|
||||
user.github_id = 1955
|
||||
user.save()
|
||||
|
||||
with patch("taiga.base.connectors.github.me") as m_me:
|
||||
m_me.return_value = ("mmcfly@bttf.com",
|
||||
github.User(id=1955,
|
||||
username="mmcfly",
|
||||
full_name="martin seamus mcfly",
|
||||
bio="time traveler"))
|
||||
|
||||
response = client.post(reverse("auth-list"), form)
|
||||
assert response.status_code == 200
|
||||
assert response.data["username"] != "mmcfly"
|
||||
assert response.data["auth_token"] != "" and response.data["auth_token"] != None
|
||||
assert response.data["email"] != "mmcfly@bttf.com"
|
||||
assert response.data["full_name"] != "martin seamus mcfly"
|
||||
assert response.data["bio"] != "time traveler"
|
||||
assert response.data["github_id"] == user.github_id
|
||||
|
||||
def test_response_200_in_registration_with_github_account_and_change_github_username(client, settings):
|
||||
settings.PUBLIC_REGISTER_ENABLED = False
|
||||
form = {"type": "github",
|
||||
"code": "xxxxxx"}
|
||||
user = factories.UserFactory()
|
||||
user.username = "mmcfly"
|
||||
user.save()
|
||||
|
||||
with patch("taiga.base.connectors.github.me") as m_me:
|
||||
m_me.return_value = ("mmcfly@bttf.com",
|
||||
github.User(id=1955,
|
||||
username="mmcfly",
|
||||
full_name="martin seamus mcfly",
|
||||
bio="time traveler"))
|
||||
|
||||
response = client.post(reverse("auth-list"), form)
|
||||
assert response.status_code == 200
|
||||
assert response.data["username"] == "mmcfly-1"
|
||||
assert response.data["auth_token"] != "" and response.data["auth_token"] != None
|
||||
assert response.data["email"] == "mmcfly@bttf.com"
|
||||
assert response.data["full_name"] == "martin seamus mcfly"
|
||||
assert response.data["bio"] == "time traveler"
|
||||
assert response.data["github_id"] == 1955
|
||||
|
||||
def test_response_200_in_registration_with_github_account_in_a_project(client, settings):
|
||||
settings.PUBLIC_REGISTER_ENABLED = False
|
||||
|
@ -171,7 +243,6 @@ def test_respond_400_if_username_or_email_is_duplicate(client, settings, registe
|
|||
response = client.post(reverse("auth-register"), register_form)
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
register_form["username"] = "username"
|
||||
register_form["email"] = "ff@dd.com"
|
||||
response = client.post(reverse("auth-register"), register_form)
|
||||
|
|
|
@ -219,7 +219,7 @@ def test_issues_event_opened_issue(client):
|
|||
"issue": {
|
||||
"title": "test-title",
|
||||
"body": "test-body",
|
||||
"number": 10,
|
||||
"html_url": "http://github.com/test/project/issues/11",
|
||||
},
|
||||
"assignee": {},
|
||||
"label": {},
|
||||
|
@ -249,7 +249,7 @@ def test_issues_event_other_than_opened_issue(client):
|
|||
"issue": {
|
||||
"title": "test-title",
|
||||
"body": "test-body",
|
||||
"number": 10,
|
||||
"html_url": "http://github.com/test/project/issues/11",
|
||||
},
|
||||
"assignee": {},
|
||||
"label": {},
|
||||
|
@ -291,17 +291,17 @@ def test_issues_event_bad_issue(client):
|
|||
|
||||
|
||||
def test_issue_comment_event_on_existing_issue_task_and_us(client):
|
||||
issue = f.IssueFactory.create(external_reference=["github", "10"])
|
||||
issue = f.IssueFactory.create(external_reference=["github", "http://github.com/test/project/issues/11"])
|
||||
take_snapshot(issue, user=issue.owner)
|
||||
task = f.TaskFactory.create(project=issue.project, external_reference=["github", "10"])
|
||||
task = f.TaskFactory.create(project=issue.project, external_reference=["github", "http://github.com/test/project/issues/11"])
|
||||
take_snapshot(task, user=task.owner)
|
||||
us = f.UserStoryFactory.create(project=issue.project, external_reference=["github", "10"])
|
||||
us = f.UserStoryFactory.create(project=issue.project, external_reference=["github", "http://github.com/test/project/issues/11"])
|
||||
take_snapshot(us, user=us.owner)
|
||||
|
||||
payload = {
|
||||
"action": "created",
|
||||
"issue": {
|
||||
"number": 10,
|
||||
"html_url": "http://github.com/test/project/issues/11",
|
||||
},
|
||||
"comment": {
|
||||
"body": "Test body",
|
||||
|
@ -346,7 +346,7 @@ def test_issue_comment_event_on_not_existing_issue_task_and_us(client):
|
|||
payload = {
|
||||
"action": "created",
|
||||
"issue": {
|
||||
"number": 11,
|
||||
"html_url": "http://github.com/test/project/issues/11",
|
||||
},
|
||||
"comment": {
|
||||
"body": "Test body",
|
||||
|
|
|
@ -200,3 +200,34 @@ def test_archived_filter(client):
|
|||
data = {"is_archived": 1}
|
||||
response = client.get(url, data)
|
||||
assert len(json.loads(response.content)) == 1
|
||||
|
||||
def test_get_total_points(client):
|
||||
project = f.ProjectFactory.create()
|
||||
|
||||
role1 = f.RoleFactory.create(project=project)
|
||||
role2 = f.RoleFactory.create(project=project)
|
||||
|
||||
points1 = f.PointsFactory.create(project=project, value=None)
|
||||
points2 = f.PointsFactory.create(project=project, value=1)
|
||||
points3 = f.PointsFactory.create(project=project, value=2)
|
||||
|
||||
us_with_points = f.UserStoryFactory.create(project=project)
|
||||
us_with_points.role_points.all().delete()
|
||||
f.RolePointsFactory.create(user_story=us_with_points, role=role1, points=points2)
|
||||
f.RolePointsFactory.create(user_story=us_with_points, role=role2, points=points3)
|
||||
|
||||
assert us_with_points.get_total_points() == 3.0
|
||||
|
||||
us_without_points = f.UserStoryFactory.create(project=project)
|
||||
us_without_points.role_points.all().delete()
|
||||
f.RolePointsFactory.create(user_story=us_without_points, role=role1, points=points1)
|
||||
f.RolePointsFactory.create(user_story=us_without_points, role=role2, points=points1)
|
||||
|
||||
assert us_without_points.get_total_points() is None
|
||||
|
||||
us_mixed = f.UserStoryFactory.create(project=project)
|
||||
us_mixed.role_points.all().delete()
|
||||
f.RolePointsFactory.create(user_story=us_mixed, role=role1, points=points1)
|
||||
f.RolePointsFactory.create(user_story=us_mixed, role=role2, points=points2)
|
||||
|
||||
assert us_mixed.get_total_points() == 1.0
|
||||
|
|
Loading…
Reference in New Issue