Skip to content

Commit

Permalink
Merge pull request #19 from davidbgk/18-issues-types-expand
Browse files Browse the repository at this point in the history
Expand issues' types and purposes, fixes #18
  • Loading branch information
davidbgk committed May 27, 2015
2 parents 8a6447d + ca9ae5c commit 14a94e8
Show file tree
Hide file tree
Showing 53 changed files with 1,218 additions and 338 deletions.
34 changes: 34 additions & 0 deletions migrations/2015-05-06-migrate-issues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
var issues = db.issue.find();

issues.map(function(issue) {
var title = issue.discussion[0].content;
title = title.trim().replace('Bonjour,', '').trim();
title = title.split('\n')[0].trim();
title = title.split('.')[0];
if (issue.type === 'other') {
var newCls = '';
// Deal with Dataset vs. Reuse.
if (issue._cls === 'Issue.DatasetIssue') {
newCls = 'Discussion.DatasetDiscussion';
} else if (issue._cls === 'Issue.ReuseIssue') {
newCls = 'Discussion.ReuseDiscussion';
}
// Create a discussion from the previous issue
// and delete the latter.
db.discussion.save({
_cls: newCls,
user: issue.user,
subject: issue.subject,
title: title,
discussion: issue.discussion,
created: issue.created,
closed: issue.closed,
closed_by: issue.closed_by
});
db.issue.remove({_id: issue._id});
} else {
// Remove the now useless type and save the new title.
db.issue.update({_id: issue._id},
{$set: {title: title}, $unset: {type: true}});
}
});
2 changes: 2 additions & 0 deletions udata/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ def init_app(app):
import udata.core.metrics.api
import udata.core.user.api
import udata.core.dataset.api
import udata.core.issues.api
import udata.core.discussions.api
import udata.core.reuse.api
import udata.core.organization.api
import udata.core.followers.api
Expand Down
4 changes: 3 additions & 1 deletion udata/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ def init_logging(app):


def register_extensions(app):
from udata import models, routing, tasks, mail, i18n, auth, theme, search, sitemap
from udata import (
models, routing, tasks, mail, i18n, auth, theme, search, sitemap
)
i18n.init_app(app)
models.init_app(app)
routing.init_app(app)
Expand Down
12 changes: 2 additions & 10 deletions udata/core/dataset/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@

from udata import fileutils, search
from udata.auth import admin_permission
from udata.api import api, fields, ModelAPI, SingleObjectAPI, API
from udata.api import api, fields, SingleObjectAPI, API
from udata.core import storages
from udata.core.issues.api import IssuesAPI
from udata.core.followers.api import FollowAPI
from udata.utils import get_by, multi_to_dict

Expand All @@ -24,10 +23,9 @@
dataset_suggestion_fields,
license_fields,
resource_fields,
resources_order,
upload_fields,
)
from .models import Dataset, Resource, DatasetIssue, FollowDataset, Checksum, License
from .models import Dataset, Resource, FollowDataset, Checksum, License
from .permissions import DatasetEditPermission
from .forms import DatasetForm, ResourceForm, DatasetFullForm
from .search import DatasetSearch
Expand Down Expand Up @@ -228,12 +226,6 @@ def delete(self, dataset, rid):
return '', 204


@ns.route('/<id>/issues/', endpoint='dataset_issues')
@api.doc(params={'id': 'The dataset ID'})
class DatasetIssuesAPI(IssuesAPI):
model = DatasetIssue


@ns.route('/<id>/followers/', endpoint='dataset_followers')
class DatasetFollowersAPI(FollowAPI):
model = FollowDataset
Expand Down
5 changes: 5 additions & 0 deletions udata/core/dataset/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from udata.models import Dataset, Reuse

from udata.core.issues.metrics import IssuesMetric
from udata.core.discussions.metrics import DiscussionsMetric
from udata.core.followers.metrics import FollowersMetric


Expand Down Expand Up @@ -34,5 +35,9 @@ class DatasetIssuesMetric(IssuesMetric):
model = Dataset


class DatasetDiscussionsMetric(DiscussionsMetric):
model = Dataset


class DatasetFollowers(FollowersMetric):
model = Dataset
10 changes: 8 additions & 2 deletions udata/core/dataset/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
from flask import url_for
from mongoengine.signals import pre_save, post_save

from udata.models import db, WithMetrics, Issue, Follow, SpatialCoverage
from udata.models import (
db, WithMetrics, Issue, Discussion, Follow, SpatialCoverage
)
from udata.i18n import lazy_gettext as _
from udata.utils import hash_url


__all__ = (
'License', 'Resource', 'Dataset', 'Checksum',
'DatasetIssue', 'FollowDataset',
'DatasetIssue', 'DatasetDiscussion', 'FollowDataset',
'UPDATE_FREQUENCIES', 'RESOURCE_TYPES',
)

Expand Down Expand Up @@ -238,5 +240,9 @@ class DatasetIssue(Issue):
subject = db.ReferenceField(Dataset)


class DatasetDiscussion(Discussion):
subject = db.ReferenceField(Dataset)


class FollowDataset(Follow):
following = db.ReferenceField(Dataset)
5 changes: 4 additions & 1 deletion udata/core/dataset/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from celery.utils.log import get_task_logger

from udata.tasks import job
from udata.models import Activity, Metrics

from udata.models import Dataset, DatasetIssue, FollowDataset, Activity, Metrics
from .models import Dataset, DatasetIssue, DatasetDiscussion, FollowDataset

log = get_task_logger(__name__)

Expand All @@ -18,6 +19,8 @@ def purge_datasets(self):
FollowDataset.objects(following=dataset).delete()
# Remove issues
DatasetIssue.objects(subject=dataset).delete()
# Remove discussions
DatasetDiscussion.objects(subject=dataset).delete()
# Remove activity
Activity.objects(related_to=dataset).delete()
# Remove metrics
Expand Down
17 changes: 9 additions & 8 deletions udata/core/dataset/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

from datetime import datetime

from flask import abort, redirect, request, url_for, g, jsonify, render_template
from flask import abort, redirect, request, url_for, jsonify, render_template
from werkzeug.contrib.atom import AtomFeed

from udata import fileutils
from udata.app import nav
from udata.auth import login_required
from udata.frontend.views import DetailView, CreateView, EditView, NestedEditView, SingleObject, SearchView, BaseView, NestedObject
from udata.i18n import I18nBlueprint, lazy_gettext as _
from udata.models import Dataset, Resource, Reuse, Issue, Follow
from udata.models import Dataset, Resource, Reuse, Issue, DatasetDiscussion, Follow

from udata.core import storages
from udata.core.site.views import current_site
Expand Down Expand Up @@ -42,12 +42,12 @@ def recent_feed():
'uri': url_for('users.show', user=dataset.owner.id, _external=True),
}
feed.add(dataset.title,
render_template('dataset/feed_item.html', dataset=dataset),
content_type='html',
author=author,
url=url_for('datasets.show', dataset=dataset.id, _external=True),
updated=dataset.last_modified,
published=dataset.created_at)
render_template('dataset/feed_item.html', dataset=dataset),
content_type='html',
author=author,
url=url_for('datasets.show', dataset=dataset.id, _external=True),
updated=dataset.last_modified,
published=dataset.created_at)
return feed.get_response()


Expand Down Expand Up @@ -100,6 +100,7 @@ def get_context(self):
context['reuses'] = Reuse.objects(datasets=self.dataset)
context['can_edit'] = DatasetEditPermission(self.dataset)
context['can_edit_resource'] = CommunityResourceEditPermission
context['discussions'] = DatasetDiscussion.objects(subject=self.dataset)
return context


Expand Down
Empty file.
132 changes: 132 additions & 0 deletions udata/core/discussions/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from datetime import datetime

from flask.ext.security import current_user

from udata.api import api, API, fields
from udata.models import Dataset, DatasetDiscussion, Reuse, ReuseDiscussion
from udata.core.user.api_fields import user_ref_fields

from .forms import DiscussionCreateForm, DiscussionCommentForm
from .models import Message, Discussion
from .permissions import CloseDiscussionPermission
from .signals import on_new_discussion, on_new_discussion_comment, on_discussion_closed

ns = api.namespace('discussions', 'Discussion related operations')

message_fields = api.model('DiscussionMessage', {
'content': fields.String(description='The message body', required=True),
'posted_by': fields.Nested(user_ref_fields, description='The message author', required=True),
'posted_on': fields.ISODateTime(description='The message posting date', required=True),
})

discussion_fields = api.model('Discussion', {
'id': fields.String(description='The discussion identifier', readonly=True),
'subject': fields.String(attribute='subject.id', description='The discussion target object identifier', required=True),
'title': fields.String(description='The discussion title', required=True),
'user': fields.Nested(user_ref_fields, description='The discussion author', required=True),
'created': fields.ISODateTime(description='The discussion creation date', readonly=True),
'closed': fields.ISODateTime(description='The discussion closing date', readonly=True),
'closed_by': fields.String(attribute='closed_by.id', description='The user who closed the discussion', readonly=True),
'discussion': fields.Nested(message_fields),
'url': fields.UrlFor('api.discussion', description='The discussion API URI', readonly=True),
})

comment_discussion_fields = api.model('DiscussionResponse', {
'comment': fields.String(description='The comment to submit', required=True),
'close': fields.Boolean(description='Is this a closing response. Only subject owner can close')
})

discussion_page_fields = api.model('DiscussionPage', fields.pager(discussion_fields))

parser = api.parser()
parser.add_argument('closed', type=bool, default=False, location='args', help='Filter closed discussions')
parser.add_argument('for', type=str, location='args', action='append', help='Filter issues for a given subject')
parser.add_argument('page', type=int, default=1, location='args', help='The page to fetch')
parser.add_argument('page_size', type=int, default=20, location='args', help='The page size to fetch')


@ns.route('/<id>/', endpoint='discussion')
@api.doc(model=discussion_fields)
class DiscussionAPI(API):
'''
Base class for a discussion thread.
'''
@api.doc('get_discussion')
@api.marshal_with(discussion_fields)
def get(self, id):
'''Get a discussion given its ID'''
discussion = Discussion.objects.get_or_404(id=id)
return discussion

@api.secure
@api.doc('comment_discussion')
@api.expect(comment_discussion_fields)
@api.response(403, 'Not allowed to close this discussion')
@api.marshal_with(discussion_fields)
def post(self, id):
'''Add comment and optionnaly close an discussion given its ID'''
discussion = Discussion.objects.get_or_404(id=id)
form = api.validate(DiscussionCommentForm)
message = Message(
content=form.comment.data,
posted_by=current_user.id
)
discussion.discussion.append(message)
close = form.close.data
if close:
CloseDiscussionPermission(discussion).test()
discussion.closed_by = current_user._get_current_object()
discussion.closed = datetime.now()
discussion.save()
if close:
on_discussion_closed.send(discussion, message=message)
else:
on_new_discussion_comment.send(discussion, message=message)
return discussion


@ns.route('/', endpoint='discussions')
class DiscussionsAPI(API):
'''
Base class for a list of discussions.
'''
@api.doc('list_discussions')
@api.marshal_with(discussion_page_fields)
@api.doc(parser=parser)
def get(self):
'''List all Discussions'''
args = parser.parse_args()
discussions = Discussion.objects
if args['for']:
discussions = discussions(subject__in=args['for'])
if not args['closed']:
discussions = discussions(closed=None)
return discussions.paginate(args['page'], args['page_size'])

@api.secure
@api.expect(discussion_fields)
@api.doc('create_discussion')
@api.marshal_with(discussion_fields)
def post(self):
'''Create a new Discussion'''
form = api.validate(DiscussionCreateForm)

message = Message(
content=form.comment.data,
posted_by=current_user.id)
if isinstance(form.subject.data, Dataset):
model = DatasetDiscussion
elif isinstance(form.subject.data, Reuse):
model = ReuseDiscussion
discussion = model.objects.create(
subject=form.subject.data.id,
title=form.title.data,
user=current_user.id,
discussion=[message]
)
on_new_discussion.send(discussion)

return discussion, 201
18 changes: 18 additions & 0 deletions udata/core/discussions/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from udata.forms import Form, fields, validators
from udata.i18n import lazy_gettext as _

__all__ = ('DiscussionCreateForm', 'DiscussionCommentForm')


class DiscussionCreateForm(Form):
title = fields.StringField(_('Title'), [validators.required()])
comment = fields.StringField(_('Comment'), [validators.required()])
subject = fields.DatasetOrReuseField(_('Subject'), [validators.required()])


class DiscussionCommentForm(Form):
comment = fields.StringField(_('Comment'), [validators.required()])
close = fields.BooleanField(default=False)
25 changes: 25 additions & 0 deletions udata/core/discussions/metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from udata.core.metrics import Metric
from udata.i18n import lazy_gettext as _

from .models import Discussion
from .signals import on_new_discussion, on_discussion_closed


class DiscussionsMetric(Metric):
name = 'discussions'
display_name = _('Discussions')

def get_value(self):
return Discussion.objects(subject=self.target, closed=None).count()


@on_new_discussion.connect
@on_discussion_closed.connect
def update_discussions_metric(discussion, **kwargs):
model = discussion.subject.__class__
for name, cls in Metric.get_for(model).items():
if issubclass(cls, DiscussionsMetric):
cls(target=discussion.subject).trigger_update()

0 comments on commit 14a94e8

Please sign in to comment.