mirror of
https://github.com/haiwen/seahub.git
synced 2025-05-10 00:47:19 +00:00
Improve code of draft (#2698)
This commit is contained in:
parent
3b1caf2140
commit
b196b1cf0a
frontend/src
seahub
tests/seahub/drafts
@ -5,8 +5,8 @@ import PropTypes from 'prop-types';
|
||||
import Prism from 'prismjs';
|
||||
/* eslint-enable */
|
||||
import { siteRoot, gettext, reviewID, draftOriginFilePath, draftFilePath, draftOriginRepoID,
|
||||
draftFileName, opStatus, publishFileVersion, originFileVersion, author, authorAvatar
|
||||
} from './utils/constants';
|
||||
draftFileName, opStatus, publishFileVersion, originFileVersion, author, authorAvatar,
|
||||
draftFileExists, originFileExists } from './utils/constants';
|
||||
import { seafileAPI } from './utils/seafile-api';
|
||||
import axios from 'axios';
|
||||
import DiffViewer from '@seafile/seafile-editor/dist/viewer/diff-viewer';
|
||||
@ -71,36 +71,77 @@ class DraftReview extends React.Component {
|
||||
}
|
||||
|
||||
initialContent = () => {
|
||||
if (publishFileVersion == 'None') {
|
||||
axios.all([
|
||||
seafileAPI.getFileDownloadLink(draftOriginRepoID, draftFilePath),
|
||||
seafileAPI.getFileDownloadLink(draftOriginRepoID, draftOriginFilePath)
|
||||
]).then(axios.spread((res1, res2) => {
|
||||
switch(this.state.reviewStatus) {
|
||||
case 'closed':
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
isShowDiff: false
|
||||
})
|
||||
break;
|
||||
case "open":
|
||||
if (!draftFileExists) {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
isShowDiff: false
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
if (!originFileExists) {
|
||||
seafileAPI.getFileDownloadLink(draftOriginRepoID, draftFilePath)
|
||||
.then(res => {
|
||||
seafileAPI.getFileContent(res.data)
|
||||
.then(res => {
|
||||
this.setState({
|
||||
draftContent: res.data,
|
||||
draftOriginContent: res.data,
|
||||
isLoading: false,
|
||||
isShowDiff: false
|
||||
});
|
||||
})
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
axios.all([
|
||||
seafileAPI.getFileContent(res1.data),
|
||||
seafileAPI.getFileContent(res2.data)
|
||||
seafileAPI.getFileDownloadLink(draftOriginRepoID, draftFilePath),
|
||||
seafileAPI.getFileDownloadLink(draftOriginRepoID, draftOriginFilePath)
|
||||
]).then(axios.spread((res1, res2) => {
|
||||
axios.all([
|
||||
seafileAPI.getFileContent(res1.data),
|
||||
seafileAPI.getFileContent(res2.data)
|
||||
]).then(axios.spread((draftContent, draftOriginContent) => {
|
||||
this.setState({
|
||||
draftContent: draftContent.data,
|
||||
draftOriginContent: draftOriginContent.data,
|
||||
isLoading: false
|
||||
});
|
||||
}));
|
||||
}));
|
||||
break;
|
||||
case "finished":
|
||||
if (!originFileExists) {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
isShowDiff: false
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
let dl0 = siteRoot + 'repo/' + draftOriginRepoID + '/' + publishFileVersion + '/download?' + 'p=' + draftOriginFilePath;
|
||||
let dl = siteRoot + 'repo/' + draftOriginRepoID + '/' + originFileVersion + '/download?' + 'p=' + draftOriginFilePath;
|
||||
axios.all([
|
||||
seafileAPI.getFileContent(dl0),
|
||||
seafileAPI.getFileContent(dl)
|
||||
]).then(axios.spread((draftContent, draftOriginContent) => {
|
||||
this.setState({
|
||||
draftContent: draftContent.data,
|
||||
draftOriginContent: draftOriginContent.data,
|
||||
isLoading: false
|
||||
isLoading: false,
|
||||
});
|
||||
}));
|
||||
}));
|
||||
} else {
|
||||
let dl0 = siteRoot + 'repo/' + draftOriginRepoID + '/' + publishFileVersion + '/download?' + 'p=' + draftOriginFilePath;
|
||||
let dl = siteRoot + 'repo/' + draftOriginRepoID + '/' + originFileVersion + '/download?' + 'p=' + draftOriginFilePath;
|
||||
axios.all([
|
||||
seafileAPI.getFileContent(dl0),
|
||||
seafileAPI.getFileContent(dl)
|
||||
]).then(axios.spread((draftContent, draftOriginContent) => {
|
||||
this.setState({
|
||||
draftContent: draftContent.data,
|
||||
draftOriginContent: draftOriginContent.data,
|
||||
isLoading: false,
|
||||
});
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -505,6 +546,182 @@ class DraftReview extends React.Component {
|
||||
this.toggleCommentDialog();
|
||||
}
|
||||
|
||||
showDiffViewer = () => {
|
||||
return (
|
||||
<div>
|
||||
{this.state.isShowDiff ?
|
||||
<DiffViewer
|
||||
newMarkdownContent={this.state.draftContent}
|
||||
oldMarkdownContent={this.state.draftOriginContent}
|
||||
ref="diffViewer"
|
||||
/>
|
||||
:
|
||||
<DiffViewer
|
||||
newMarkdownContent={this.state.draftContent}
|
||||
oldMarkdownContent={this.state.draftContent}
|
||||
ref="diffViewer"
|
||||
/>
|
||||
}
|
||||
<i className="fa fa-plus-square review-comment-btn" ref="commentbtn" onMouseDown={this.addComment}></i>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderContent = () => {
|
||||
switch(this.state.reviewStatus) {
|
||||
case "closed":
|
||||
return <p className="error">{gettext('The review has been closed.')}</p>;
|
||||
case "open":
|
||||
if (!draftFileExists) {
|
||||
return <p className="error">{gettext('Draft has been deleted.')}</p>;
|
||||
}
|
||||
return this.showDiffViewer();
|
||||
case "finished":
|
||||
if (!originFileExists) {
|
||||
return <p className="error">{gettext('Original file has been deleted.')}</p>
|
||||
}
|
||||
return this.showDiffViewer();
|
||||
}
|
||||
}
|
||||
|
||||
showDiffButton = () => {
|
||||
return (
|
||||
<div className={'seafile-toggle-diff'}>
|
||||
<label className="custom-switch" id="toggle-diff">
|
||||
<input type="checkbox" checked={this.state.isShowDiff && 'checked'}
|
||||
name="option" className="custom-switch-input"
|
||||
onChange={this.onSwitchShowDiff}/>
|
||||
<span className="custom-switch-indicator"></span>
|
||||
</label>
|
||||
<Tooltip placement="bottom" isOpen={this.state.showDiffTip}
|
||||
target="toggle-diff" toggle={this.toggleDiffTip}>
|
||||
{gettext('View diff')}</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderDiffButton = () => {
|
||||
switch(this.state.reviewStatus) {
|
||||
case "closed":
|
||||
return;
|
||||
case "open":
|
||||
if (!draftFileExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!originFileExists) {
|
||||
return;
|
||||
}
|
||||
return this.showDiffButton();
|
||||
case "finished":
|
||||
if (!originFileExists) {
|
||||
return;
|
||||
}
|
||||
return this.showDiffButton();
|
||||
}
|
||||
}
|
||||
|
||||
renderGo = (OriginFileLink, draftLink) => {
|
||||
let viewFile = <a href={OriginFileLink} className="view-file-link">{gettext('View File')}</a>;
|
||||
let editDraft = <a href={draftLink} className="draft-link">{gettext('Edit draft')}</a>;
|
||||
switch(this.state.reviewStatus) {
|
||||
case "closed":
|
||||
return viewFile;
|
||||
case "open":
|
||||
if (!draftFileExists) {
|
||||
return viewFile;
|
||||
}
|
||||
|
||||
return editDraft;
|
||||
case "finished":
|
||||
if (!originFileExists) {
|
||||
return;
|
||||
}
|
||||
return viewFile;
|
||||
}
|
||||
}
|
||||
|
||||
showNavItem = (showTab) => {
|
||||
switch(showTab) {
|
||||
case "info":
|
||||
return (
|
||||
<NavItem className="nav-item">
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'reviewInfo' })}
|
||||
onClick={() => { this.tabItemClick('reviewInfo');}}
|
||||
>
|
||||
<i className="fas fa-info-circle"></i>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
);
|
||||
case "comments":
|
||||
return (
|
||||
<NavItem className="nav-item">
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'comments' })}
|
||||
onClick={() => {this.tabItemClick('comments');}}
|
||||
>
|
||||
<i className="fa fa-comments"></i>
|
||||
{ this.state.commentsNumber > 0 &&
|
||||
<div className='comments-number'>{this.state.commentsNumber}</div>}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
);
|
||||
case "history":
|
||||
return (
|
||||
<NavItem className="nav-item">
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'history' })}
|
||||
onClick={() => { this.tabItemClick('history');}}
|
||||
>
|
||||
<i className="fas fa-history"></i>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderNavItems = () => {
|
||||
switch(this.state.reviewStatus) {
|
||||
case "closed":
|
||||
return (
|
||||
<Nav tabs className="review-side-panel-nav">
|
||||
{this.showNavItem("info")}
|
||||
</Nav>
|
||||
);
|
||||
case "open":
|
||||
if (!draftFileExists) {
|
||||
return (
|
||||
<Nav tabs className="review-side-panel-nav">
|
||||
{this.showNavItem("info")}
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Nav tabs className="review-side-panel-nav">
|
||||
{this.showNavItem('info')}
|
||||
{this.showNavItem('comments')}
|
||||
{this.showNavItem('history')}
|
||||
</Nav>
|
||||
);
|
||||
case "finished":
|
||||
if (!originFileExists) {
|
||||
return (
|
||||
<Nav tabs className="review-side-panel-nav">
|
||||
{this.showNavItem("info")}
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Nav tabs className="review-side-panel-nav">
|
||||
{this.showNavItem('info')}
|
||||
{this.showNavItem('comments')}
|
||||
</Nav>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const onResizeMove = this.state.inResizing ? this.onResizeMouseMove : null;
|
||||
const draftLink = siteRoot + 'lib/' + draftOriginRepoID + '/file' + draftFilePath + '?mode=edit';
|
||||
@ -520,30 +737,20 @@ class DraftReview extends React.Component {
|
||||
<React.Fragment>
|
||||
<span className="file-name">{draftFileName}</span>
|
||||
<span className="file-copywriting">{gettext('review')}</span>
|
||||
{ opStatus == 'open' && <a href={draftLink} className="draft-link">{gettext('Edit draft')}</a>}
|
||||
{ opStatus !== 'open' && <a href={OriginFileLink} className="view-file-link">{gettext('View File')}</a>}
|
||||
{this.renderGo(OriginFileLink, draftLink)}
|
||||
</React.Fragment>
|
||||
</div>
|
||||
</div>
|
||||
<div className="button-group">
|
||||
<div className={'seafile-toggle-diff'}>
|
||||
<label className="custom-switch" id="toggle-diff">
|
||||
<input type="checkbox" checked={this.state.isShowDiff && 'checked'}
|
||||
name="option" className="custom-switch-input"
|
||||
onChange={this.onSwitchShowDiff}/>
|
||||
<span className="custom-switch-indicator"></span>
|
||||
</label>
|
||||
<Tooltip placement="bottom" isOpen={this.state.showDiffTip}
|
||||
target="toggle-diff" toggle={this.toggleDiffTip}>
|
||||
{gettext('View diff')}</Tooltip>
|
||||
</div>
|
||||
{this.renderDiffButton()}
|
||||
{
|
||||
this.state.reviewStatus === 'open' &&
|
||||
<div className="cur-file-operation">
|
||||
<button className='btn btn-secondary file-operation-btn' title={gettext('Close review')}
|
||||
onClick={this.onCloseReview}>{gettext('Close')}</button>
|
||||
<button className='btn btn-success file-operation-btn' title={gettext('Publish draft')}
|
||||
{ draftFileExists && <button className='btn btn-success file-operation-btn' title={gettext('Publish draft')}
|
||||
onClick={this.onPublishReview}>{gettext('Publish')}</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
@ -567,56 +774,14 @@ class DraftReview extends React.Component {
|
||||
</div>
|
||||
:
|
||||
<div className="markdown-viewer-render-content article" ref="mainPanel">
|
||||
{this.state.isShowDiff ?
|
||||
<DiffViewer
|
||||
newMarkdownContent={this.state.draftContent}
|
||||
oldMarkdownContent={this.state.draftOriginContent}
|
||||
ref="diffViewer"
|
||||
/>
|
||||
:
|
||||
<DiffViewer
|
||||
newMarkdownContent={this.state.draftContent}
|
||||
oldMarkdownContent={this.state.draftContent}
|
||||
ref="diffViewer"
|
||||
/>
|
||||
}
|
||||
<i className="fa fa-plus-square review-comment-btn" ref="commentbtn" onMouseDown={this.addComment}></i>
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="cur-view-right-part" style={{width:(this.state.commentWidth)+'%'}}>
|
||||
<div className="seafile-comment-resize" onMouseDown={this.onResizeMouseDown}></div>
|
||||
<div className="review-side-panel">
|
||||
<Nav tabs className="review-side-panel-nav">
|
||||
<NavItem className="nav-item">
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'reviewInfo' })}
|
||||
onClick={() => { this.tabItemClick('reviewInfo');}}
|
||||
>
|
||||
<i className="fas fa-info-circle"></i>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem className="nav-item">
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'comments' })}
|
||||
onClick={() => { this.tabItemClick('comments');}}
|
||||
>
|
||||
<i className="fa fa-comments"></i>
|
||||
{ this.state.commentsNumber > 0 &&
|
||||
<div className='comments-number'>{this.state.commentsNumber}</div>}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
{ this.state.reviewStatus == 'finished' ? '':
|
||||
<NavItem className="nav-item">
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'history' })}
|
||||
onClick={() => { this.tabItemClick('history');}}
|
||||
>
|
||||
<i className="fas fa-history"></i>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
}
|
||||
</Nav>
|
||||
{this.renderNavItems()}
|
||||
<TabContent activeTab={this.state.activeTab}>
|
||||
<TabPane tabId="reviewInfo">
|
||||
<div className="review-side-panel-body">
|
||||
|
@ -64,3 +64,5 @@ export const publishFileVersion = window.draftReview ? window.draftReview.config
|
||||
export const originFileVersion = window.draftReview ? window.draftReview.config.originFileVersion : '';
|
||||
export const author = window.draftReview ? window.draftReview.config.author : '';
|
||||
export const authorAvatar = window.draftReview ? window.draftReview.config.authorAvatar : '';
|
||||
export const originFileExists = window.draftReview ? window.draftReview.config.originFileExists : '';
|
||||
export const draftFileExists = window.draftReview ? window.draftReview.config.draftFileExists : '';
|
||||
|
@ -18,7 +18,7 @@ from seahub.constants import PERMISSION_READ_WRITE
|
||||
from seahub.views import check_folder_permission
|
||||
|
||||
from seahub.drafts.models import Draft, DraftReview, DraftReviewExist, \
|
||||
DraftFileConflict, ReviewReviewer
|
||||
DraftFileConflict, ReviewReviewer, OriginalFileConflict
|
||||
from seahub.drafts.signals import update_review_successful
|
||||
|
||||
|
||||
@ -62,21 +62,21 @@ class DraftReviewsView(APIView):
|
||||
return api_error(status.HTTP_404_NOT_FOUND,
|
||||
'Draft %s not found.' % draft_id)
|
||||
|
||||
origin_repo_id = d.origin_repo_id
|
||||
file_path = d.draft_file_path
|
||||
draft_file = seafile_api.get_file_id_by_path(origin_repo_id, file_path)
|
||||
if not draft_file:
|
||||
error_msg = 'Draft file not found.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
# perm check
|
||||
perm = check_folder_permission(request, d.origin_repo_id, '/')
|
||||
perm = check_folder_permission(request, origin_repo_id, '/')
|
||||
|
||||
if perm is None:
|
||||
return api_error(status.HTTP_403_FORBIDDEN,
|
||||
'Permission denied.')
|
||||
|
||||
username = request.user.username
|
||||
try:
|
||||
d_r = DraftReview.objects.get(creator=username, draft_id=d)
|
||||
if d_r.status == 'closed':
|
||||
d_r.delete()
|
||||
except DraftReview.DoesNotExist:
|
||||
pass
|
||||
|
||||
try:
|
||||
d_r = DraftReview.objects.add(creator=username, draft=d)
|
||||
except (DraftReviewExist):
|
||||
@ -129,7 +129,14 @@ class DraftReviewView(APIView):
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
r.publish(operator=username)
|
||||
try:
|
||||
r.publish(operator=username)
|
||||
except DraftFileConflict:
|
||||
error_msg = 'There is a conflict between the draft and the original file.'
|
||||
return api_error(status.HTTP_409_CONFLICT, error_msg)
|
||||
except OriginalFileConflict:
|
||||
error_msg = 'Original file not found.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
reviewers = ReviewReviewer.objects.filter(review_id=r)
|
||||
# send notice to other reviewers if has
|
||||
|
@ -40,13 +40,14 @@ class DraftsView(APIView):
|
||||
"""List all user drafts.
|
||||
"""
|
||||
username = request.user.username
|
||||
data = [x.to_dict() for x in Draft.objects.filter(username=username)]
|
||||
data = Draft.objects.list_draft_by_username(username)
|
||||
|
||||
draft_counts = len(data)
|
||||
|
||||
result = {}
|
||||
result['data'] = data
|
||||
result['draft_counts'] = draft_counts
|
||||
|
||||
return Response(result)
|
||||
|
||||
@add_org_context
|
||||
@ -107,8 +108,7 @@ class DraftView(APIView):
|
||||
# perm check
|
||||
repo_id = d.origin_repo_id
|
||||
uuid = d.origin_file_uuid
|
||||
file_path = posixpath.join(uuid.parent_path, uuid.filename)
|
||||
perm = check_folder_permission(request, repo_id, file_path)
|
||||
perm = check_folder_permission(request, repo_id, uuid.parent_path)
|
||||
|
||||
if perm != PERMISSION_READ_WRITE:
|
||||
error_msg = 'Permission denied.'
|
||||
|
@ -23,7 +23,67 @@ class DraftFileConflict(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OriginalFileConflict(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DraftManager(models.Manager):
|
||||
def list_draft_by_username(self, username, with_reviews=True):
|
||||
"""list all user drafts
|
||||
If with_reviews is true, return the draft associated review
|
||||
"""
|
||||
repo_cache = {}
|
||||
|
||||
def get_repo_with_cache(repo_id, repo_cache):
|
||||
"""return repo object
|
||||
Avoid loading the same repo multiple times
|
||||
"""
|
||||
if repo_id in repo_cache:
|
||||
return repo_cache[repo_id]
|
||||
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
repo_cache[repo_id] = repo
|
||||
return repo
|
||||
|
||||
data = []
|
||||
qs = self.filter(username=username)
|
||||
if with_reviews:
|
||||
qs_r = list(DraftReview.objects.filter(draft_id__in=qs))
|
||||
|
||||
for d in qs:
|
||||
# If repo does not exist, no related items are displayed.
|
||||
repo = get_repo_with_cache(d.origin_repo_id, repo_cache)
|
||||
if not repo:
|
||||
continue
|
||||
|
||||
uuid = d.origin_file_uuid
|
||||
file_path = posixpath.join(uuid.parent_path, uuid.filename)
|
||||
|
||||
draft = {}
|
||||
# Query whether there is an associated review
|
||||
if with_reviews:
|
||||
draft['review_id'] = None
|
||||
draft['review_status'] = None
|
||||
for r in qs_r:
|
||||
if r.draft_id == d:
|
||||
draft['review_id'] = r.id
|
||||
draft['review_status'] = r.status
|
||||
|
||||
draft['id'] = d.id
|
||||
draft['owner'] = d.username
|
||||
draft['repo_name'] = repo.name
|
||||
draft['owner_nickname'] = email2nickname(d.username)
|
||||
draft['origin_repo_id'] = d.origin_repo_id
|
||||
draft['origin_file_path'] = file_path
|
||||
draft['origin_file_version'] = d.origin_file_version
|
||||
draft['draft_file_path'] = d.draft_file_path
|
||||
draft['created_at'] = datetime_to_isoformat_timestr(d.created_at)
|
||||
draft['updated_at'] = datetime_to_isoformat_timestr(d.updated_at)
|
||||
|
||||
data.append(draft)
|
||||
|
||||
return data
|
||||
|
||||
def create_exist_file_draft(self, repo, username, file_uuid, file_path):
|
||||
# create drafts dir if does not exist
|
||||
draft_dir_id = seafile_api.get_dir_id_by_path(repo.id, '/Drafts')
|
||||
@ -36,16 +96,41 @@ class DraftManager(models.Manager):
|
||||
draft_file_path = '/Drafts/' + draft_file_name
|
||||
|
||||
try:
|
||||
# Determine if there is a draft of the file
|
||||
d = self.get(origin_file_uuid=file_uuid)
|
||||
except Draft.DoesNotExist:
|
||||
try:
|
||||
# Determine if there is a draft with the same name as
|
||||
# the generated draft file path
|
||||
d_2 = self.get(origin_repo_id=repo.id, draft_file_path=draft_file_path)
|
||||
d_2.delete(operator=username)
|
||||
except Draft.DoesNotExist:
|
||||
pass
|
||||
|
||||
# copy file to draft dir
|
||||
seafile_api.copy_file(repo.id, file_uuid.parent_path, file_uuid.filename,
|
||||
repo.id, '/Drafts', draft_file_name,
|
||||
username=username, need_progress=0, synchronous=1)
|
||||
|
||||
return draft_file_path
|
||||
|
||||
if d:
|
||||
raise DraftFileExist
|
||||
file_id = seafile_api.get_file_id_by_path(repo.id, d.draft_file_path)
|
||||
# If the database entry exists and the draft file exists,
|
||||
# then raise DraftFileExist
|
||||
if file_id:
|
||||
raise DraftFileExist
|
||||
# If the database entry exists and the draft file does not exist,
|
||||
# delete the database entry
|
||||
else:
|
||||
d.delete(operator=username)
|
||||
|
||||
# copy file to draft dir
|
||||
seafile_api.copy_file(repo.id, file_uuid.parent_path, file_uuid.filename,
|
||||
repo.id, '/Drafts', draft_file_name,
|
||||
username=username, need_progress=0, synchronous=1)
|
||||
|
||||
return draft_file_path
|
||||
|
||||
def add(self, username, repo, file_path, file_exist=True, file_id=None, org_id=-1):
|
||||
file_path = normalize_file_path(file_path)
|
||||
@ -90,7 +175,7 @@ class Draft(TimestampedModel):
|
||||
draft_file_name, operator)
|
||||
|
||||
if hasattr(self, 'draftreview'):
|
||||
if self.draftreview.status == 'closed':
|
||||
if self.draftreview.status != 'finished':
|
||||
self.draftreview.delete()
|
||||
|
||||
super(Draft, self).delete()
|
||||
@ -108,48 +193,44 @@ class Draft(TimestampedModel):
|
||||
|
||||
file_id = seafile_api.get_file_id_by_path(self.origin_repo_id,
|
||||
origin_file_path)
|
||||
if not file_id:
|
||||
raise DraftFileConflict
|
||||
|
||||
draft_file_name = os.path.basename(self.draft_file_path)
|
||||
draft_file_path = os.path.dirname(self.draft_file_path)
|
||||
|
||||
file_name = self.origin_file_uuid.filename
|
||||
|
||||
if file_id != self.origin_file_version and self.draft_file_path != origin_file_path:
|
||||
raise DraftFileConflict
|
||||
if file_id:
|
||||
if file_id != self.origin_file_version and self.draft_file_path != origin_file_path:
|
||||
raise DraftFileConflict
|
||||
|
||||
if self.draft_file_path == origin_file_path:
|
||||
f = os.path.splitext(draft_file_name)[0][:-7]
|
||||
file_type = os.path.splitext(draft_file_name)[-1]
|
||||
file_name = f + file_type
|
||||
if self.draft_file_path == origin_file_path:
|
||||
f = os.path.splitext(draft_file_name)[0][:-7]
|
||||
file_type = os.path.splitext(draft_file_name)[-1]
|
||||
file_name = f + file_type
|
||||
|
||||
# move draft file to origin file
|
||||
seafile_api.move_file(
|
||||
self.origin_repo_id, draft_file_path, draft_file_name,
|
||||
self.origin_repo_id, self.origin_file_uuid.parent_path,
|
||||
file_name, replace=1,
|
||||
username=operator, need_progress=0, synchronous=1
|
||||
)
|
||||
# move draft file to origin file
|
||||
seafile_api.move_file(
|
||||
self.origin_repo_id, draft_file_path, draft_file_name,
|
||||
self.origin_repo_id, self.origin_file_uuid.parent_path,
|
||||
file_name, replace=1,
|
||||
username=operator, need_progress=0, synchronous=1
|
||||
)
|
||||
|
||||
else:
|
||||
# move draft file to origin file
|
||||
seafile_api.move_file(
|
||||
self.origin_repo_id, draft_file_path, draft_file_name,
|
||||
self.origin_repo_id, self.origin_file_uuid.parent_path,
|
||||
file_name, replace=1,
|
||||
username=operator, need_progress=0, synchronous=1
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
uuid = self.origin_file_uuid
|
||||
file_path = posixpath.join(uuid.parent_path, uuid.filename)
|
||||
|
||||
repo = seafile_api.get_repo(self.origin_repo_id)
|
||||
|
||||
review_id = None
|
||||
review_status = None
|
||||
if hasattr(self, 'draftreview'):
|
||||
review_id = self.draftreview.id
|
||||
review_status = self.draftreview.status
|
||||
|
||||
return {
|
||||
'id': self.pk,
|
||||
'review_id': review_id,
|
||||
'review_status': review_status,
|
||||
'owner': self.username,
|
||||
'repo_name': repo.name,
|
||||
'owner_nickname': email2nickname(self.username),
|
||||
'origin_repo_id': self.origin_repo_id,
|
||||
'origin_file_path': file_path,
|
||||
@ -166,10 +247,14 @@ class DraftReviewExist(Exception):
|
||||
|
||||
class DraftReviewManager(models.Manager):
|
||||
def add(self, creator, draft):
|
||||
|
||||
has_review = hasattr(draft, 'draftreview')
|
||||
if has_review:
|
||||
raise DraftReviewExist
|
||||
try:
|
||||
d_r = self.get(creator=creator, draft_id=draft)
|
||||
if d_r.status == 'closed':
|
||||
d_r.delete()
|
||||
if d_r.status == 'open':
|
||||
raise DraftReviewExist
|
||||
except DraftReview.DoesNotExist:
|
||||
pass
|
||||
|
||||
draft_review = self.model(creator=creator,
|
||||
author=draft.username,
|
||||
@ -183,33 +268,90 @@ class DraftReviewManager(models.Manager):
|
||||
|
||||
return draft_review
|
||||
|
||||
def get_reviews_by_creator_and_status(self, creator, status):
|
||||
def get_reviews_by_creator_and_status(self, creator, status, with_reviewers=True):
|
||||
"""
|
||||
List all reviews as creator according to a certain status
|
||||
"""
|
||||
repo_cache = {}
|
||||
|
||||
def get_repo_with_cache(repo_id, repo_cache):
|
||||
"""return repo object
|
||||
Avoid loading the same repo multiple times
|
||||
"""
|
||||
if repo_id in repo_cache:
|
||||
return repo_cache[repo_id]
|
||||
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
repo_cache[repo_id] = repo
|
||||
|
||||
return repo
|
||||
|
||||
from seahub.api2.utils import user_to_dict
|
||||
reviews = self.filter(creator=creator, status=status)
|
||||
reviewers = ReviewReviewer.objects.filter(review_id__in=reviews)
|
||||
|
||||
if with_reviewers:
|
||||
reviewers = ReviewReviewer.objects.filter(review_id__in=reviews)
|
||||
|
||||
data = []
|
||||
for review in reviews:
|
||||
reviewer_list = []
|
||||
for r in reviewers:
|
||||
if review.id == r.review_id_id:
|
||||
reviewer = user_to_dict(r.reviewer, avatar_size=64)
|
||||
reviewer_list.append(reviewer)
|
||||
|
||||
if with_reviewers:
|
||||
reviewer_list = []
|
||||
for r in reviewers:
|
||||
if review.id == r.review_id_id:
|
||||
reviewer = user_to_dict(r.reviewer, avatar_size=64)
|
||||
reviewer_list.append(reviewer)
|
||||
|
||||
author = user_to_dict(review.creator, avatar_size=64)
|
||||
|
||||
review = review.to_dict()
|
||||
review.update({'reviewers': reviewer_list})
|
||||
review.update({'author': author})
|
||||
data.append(review)
|
||||
# If repo does not exist, no related items are displayed.
|
||||
repo = get_repo_with_cache(review.origin_repo_id, repo_cache)
|
||||
if not repo:
|
||||
continue
|
||||
|
||||
review_obj = {}
|
||||
review_obj['id'] = review.id
|
||||
review_obj['creator'] = review.creator
|
||||
review_obj['status'] = review.status
|
||||
review_obj['creator_name'] = email2nickname(review.creator)
|
||||
review_obj['draft_origin_repo_id'] = review.origin_repo_id
|
||||
review_obj['draft_origin_repo_name'] = repo.name
|
||||
review_obj['draft_origin_file_version'] = review.origin_file_version
|
||||
review_obj['draft_publish_file_version'] = review.publish_file_version
|
||||
review_obj['draft_file_path'] = review.draft_file_path
|
||||
review_obj['created_at'] = datetime_to_isoformat_timestr(review.created_at)
|
||||
review_obj['updated_at'] = datetime_to_isoformat_timestr(review.updated_at)
|
||||
|
||||
if review_obj and with_reviewers:
|
||||
review_obj.update({'reviewers': reviewer_list})
|
||||
|
||||
if review_obj:
|
||||
review_obj.update({'author': author})
|
||||
data.append(review_obj)
|
||||
|
||||
return data
|
||||
|
||||
def get_reviews_by_reviewer_and_status(self, reviewer, status):
|
||||
"""
|
||||
List all reviews as reviewers according to a certain status
|
||||
"""
|
||||
repo_cache = {}
|
||||
|
||||
def get_repo_with_cache(repo_id, repo_cache):
|
||||
"""return repo object
|
||||
Avoid loading the same repo multiple times
|
||||
"""
|
||||
if repo_id in repo_cache:
|
||||
return repo_cache[repo_id]
|
||||
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
repo_cache[repo_id] = repo
|
||||
|
||||
return repo
|
||||
|
||||
from seahub.api2.utils import user_to_dict
|
||||
reviews = self.filter(reviewreviewer__reviewer=reviewer, status=status)
|
||||
|
||||
reviewers = ReviewReviewer.objects.filter(review_id__in=reviews)
|
||||
|
||||
data = []
|
||||
@ -222,10 +364,28 @@ class DraftReviewManager(models.Manager):
|
||||
|
||||
author = user_to_dict(review.creator, avatar_size=64)
|
||||
|
||||
review = review.to_dict()
|
||||
review.update({'author': author})
|
||||
review.update({'reviewers': reviewer_list})
|
||||
data.append(review)
|
||||
# If repo does not exist, no related items are displayed.
|
||||
repo = get_repo_with_cache(review.origin_repo_id, repo_cache)
|
||||
if not repo:
|
||||
continue
|
||||
|
||||
review_obj = {}
|
||||
review_obj['id'] = review.id
|
||||
review_obj['creator'] = review.creator
|
||||
review_obj['status'] = review.status
|
||||
review_obj['creator_name'] = email2nickname(review.creator)
|
||||
review_obj['draft_origin_repo_id'] = review.origin_repo_id
|
||||
review_obj['draft_origin_repo_name'] = repo.name
|
||||
review_obj['draft_origin_file_version'] = review.origin_file_version
|
||||
review_obj['draft_publish_file_version'] = review.publish_file_version
|
||||
review_obj['draft_file_path'] = review.draft_file_path
|
||||
review_obj['created_at'] = datetime_to_isoformat_timestr(review.created_at)
|
||||
review_obj['updated_at'] = datetime_to_isoformat_timestr(review.updated_at)
|
||||
|
||||
if review_obj:
|
||||
review_obj.update({'reviewers': reviewer_list})
|
||||
review_obj.update({'author': author})
|
||||
data.append(review_obj)
|
||||
|
||||
return data
|
||||
|
||||
@ -244,17 +404,12 @@ class DraftReview(TimestampedModel):
|
||||
objects = DraftReviewManager()
|
||||
|
||||
def to_dict(self):
|
||||
r_repo = seafile_api.get_repo(self.origin_repo_id)
|
||||
if not r_repo:
|
||||
raise DraftFileConflict
|
||||
|
||||
return {
|
||||
'id': self.pk,
|
||||
'creator': self.creator,
|
||||
'status': self.status,
|
||||
'creator_name': email2nickname(self.creator),
|
||||
'draft_origin_repo_id': self.origin_repo_id,
|
||||
'draft_origin_repo_name': r_repo.name,
|
||||
'draft_origin_file_version': self.origin_file_version,
|
||||
'draft_publish_file_version': self.publish_file_version,
|
||||
'draft_file_path': self.draft_file_path,
|
||||
@ -298,6 +453,10 @@ class DraftReview(TimestampedModel):
|
||||
|
||||
# get draft published version
|
||||
file_id = seafile_api.get_file_id_by_path(self.origin_repo_id, origin_file_path)
|
||||
|
||||
if not file_id:
|
||||
raise OriginalFileConflict
|
||||
|
||||
self.publish_file_version = file_id
|
||||
self.status = 'finished'
|
||||
self.save()
|
||||
|
@ -54,8 +54,11 @@ def has_draft_file(repo_id, file_path):
|
||||
from .models import Draft
|
||||
if file_uuid:
|
||||
try:
|
||||
Draft.objects.get(origin_file_uuid=file_uuid)
|
||||
has_draft = True
|
||||
d = Draft.objects.get(origin_file_uuid=file_uuid)
|
||||
file_id = seafile_api.get_file_id_by_path(repo_id, d.draft_file_path)
|
||||
if file_id:
|
||||
has_draft = True
|
||||
|
||||
except Draft.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
@ -4,11 +4,12 @@ import posixpath
|
||||
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.utils.translation import ugettext as _
|
||||
from seaserv import seafile_api
|
||||
|
||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||
from seahub.auth.decorators import login_required
|
||||
from seahub.views import check_folder_permission
|
||||
from seahub.utils import render_permission_error
|
||||
from seahub.utils import render_permission_error, render_error
|
||||
from seahub.drafts.models import Draft, DraftReview
|
||||
from seahub.api2.utils import user_to_dict
|
||||
|
||||
@ -29,15 +30,22 @@ def review(request, pk):
|
||||
|
||||
# check perm
|
||||
uuid = d_r.origin_file_uuid
|
||||
file_path = posixpath.join(uuid.parent_path, uuid.filename)
|
||||
|
||||
origin_repo_id = d_r.origin_repo_id
|
||||
|
||||
permission = check_folder_permission(request, origin_repo_id, '/')
|
||||
|
||||
if not permission:
|
||||
return render_permission_error(request, _(u'Permission denied.'))
|
||||
|
||||
origin_file_path = posixpath.join(uuid.parent_path, uuid.filename)
|
||||
origin_file = seafile_api.get_file_id_by_path(origin_repo_id, origin_file_path)
|
||||
origin_file_exists = True
|
||||
if not origin_file:
|
||||
origin_file_exists = False
|
||||
|
||||
draft_file = seafile_api.get_file_id_by_path(origin_repo_id, d_r.draft_file_path)
|
||||
draft_file_exists = True
|
||||
if not draft_file:
|
||||
draft_file_exists = False
|
||||
|
||||
draft_file_name = os.path.basename(d_r.draft_file_path)
|
||||
|
||||
author_info = user_to_dict(d_r.author, avatar_size=32)
|
||||
@ -47,7 +55,7 @@ def review(request, pk):
|
||||
"review_id": pk,
|
||||
"draft_repo_id": d_r.origin_repo_id,
|
||||
"draft_origin_repo_id": d_r.origin_repo_id,
|
||||
"draft_origin_file_path": file_path,
|
||||
"draft_origin_file_path": origin_file_path,
|
||||
"draft_file_path": d_r.draft_file_path,
|
||||
"draft_file_name": draft_file_name,
|
||||
"origin_file_version": d_r.origin_file_version,
|
||||
@ -55,5 +63,7 @@ def review(request, pk):
|
||||
"status": d_r.status,
|
||||
"permission": permission,
|
||||
"author": author_info['user_name'],
|
||||
'author_avatar_url': author_info['avatar_url']
|
||||
"author_avatar_url": author_info['avatar_url'],
|
||||
"origin_file_exists": origin_file_exists,
|
||||
"draft_file_exists": draft_file_exists
|
||||
})
|
||||
|
@ -18,6 +18,8 @@
|
||||
originFileVersion: '{{ origin_file_version }}',
|
||||
author: '{{ author }}',
|
||||
authorAvatar: '{{ author_avatar_url }}',
|
||||
originFileExists: '{{ origin_file_exists }}' === 'True',
|
||||
draftFileExists: '{{ draft_file_exists }}' === 'True',
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -1,3 +1,4 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from seahub.drafts.models import Draft, DraftReview, ReviewReviewer, \
|
||||
DraftFileExist
|
||||
from seahub.test_utils import BaseTestCase
|
||||
@ -6,6 +7,52 @@ from seaserv import seafile_api
|
||||
|
||||
|
||||
class DraftManagerTest(BaseTestCase):
|
||||
def test_list_draft_by_username(self):
|
||||
assert len(Draft.objects.all()) == 0
|
||||
Draft.objects.add(self.user.username, self.repo, self.file)
|
||||
|
||||
draft_list = Draft.objects.list_draft_by_username(self.user.username)
|
||||
|
||||
assert len(draft_list) == 1
|
||||
|
||||
def test_list_draft_by_username_with_invalid_repo(self):
|
||||
self.login_as(self.user)
|
||||
assert len(Draft.objects.all()) == 0
|
||||
Draft.objects.add(self.user.username, self.repo, self.file)
|
||||
|
||||
url = reverse('api2-repo', args=[self.repo.id])
|
||||
resp = self.client.delete(url, {}, 'application/x-www-form-urlencoded')
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
draft_list = Draft.objects.list_draft_by_username(self.user.username)
|
||||
|
||||
assert len(draft_list) == 0
|
||||
assert len(Draft.objects.all()) == 1
|
||||
|
||||
def test_list_draft_by_username_with_invalid_origin_file(self):
|
||||
self.login_as(self.user)
|
||||
assert len(Draft.objects.all()) == 0
|
||||
|
||||
url = reverse('api-v2.1-drafts')
|
||||
resp = self.client.post(url, {
|
||||
'repo_id': self.repo.id,
|
||||
'file_path': self.file,
|
||||
})
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
file_url = reverse('api-v2.1-file-view', args=[self.repo.id])
|
||||
d_resp = self.client.delete(file_url + '?p=' + self.file,
|
||||
{}, 'application/x-www-form-urlencoded')
|
||||
|
||||
self.assertEqual(200, d_resp.status_code)
|
||||
|
||||
draft_list = Draft.objects.list_draft_by_username(self.user.username)
|
||||
|
||||
assert len(draft_list) == 1
|
||||
assert len(Draft.objects.all()) == 1
|
||||
|
||||
def test_add(self):
|
||||
assert len(Draft.objects.all()) == 0
|
||||
draft = Draft.objects.add(self.user.username, self.repo, self.file)
|
||||
@ -83,6 +130,32 @@ class DraftReviewManagerTest(BaseTestCase):
|
||||
DraftReview.objects.add(self.user.username, self.draft)
|
||||
assert(len(DraftReview.objects.get_reviews_by_creator_and_status(self.user.username, 'open')) == 1)
|
||||
|
||||
def test_get_reviews_by_creator_and_status_with_invalid_repo(self):
|
||||
self.login_as(self.user)
|
||||
assert(len(DraftReview.objects.get_reviews_by_creator_and_status(self.user.username, 'open')) == 0)
|
||||
DraftReview.objects.add(self.user.username, self.draft)
|
||||
|
||||
resp = self.client.delete(
|
||||
reverse('api2-repo', args=[self.repo.id])
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
assert(len(DraftReview.objects.all()) == 1)
|
||||
assert(len(DraftReview.objects.get_reviews_by_creator_and_status(self.user.username, 'open')) == 0)
|
||||
|
||||
def test_get_reviews_by_creator_and_status_with_invalid_origin_file(self):
|
||||
self.login_as(self.user)
|
||||
assert(len(DraftReview.objects.get_reviews_by_creator_and_status(self.user.username, 'open')) == 0)
|
||||
|
||||
DraftReview.objects.add(self.user.username, self.draft)
|
||||
url = reverse('api-v2.1-file-view', args=[self.repo.id])
|
||||
d_resp = self.client.delete(url + '?p=' + self.file,
|
||||
{}, 'application/x-www-form-urlencoded')
|
||||
self.assertEqual(200, d_resp.status_code)
|
||||
|
||||
assert(len(DraftReview.objects.all()) == 1)
|
||||
assert(len(DraftReview.objects.get_reviews_by_creator_and_status(self.user.username, 'open')) == 1)
|
||||
|
||||
def test_get_reviews_by_reviewer_and_status(self):
|
||||
assert(len(DraftReview.objects.get_reviews_by_reviewer_and_status('foo@foo.com', 'open')) == 0)
|
||||
review = DraftReview.objects.add(self.user.username, self.draft)
|
||||
|
Loading…
Reference in New Issue
Block a user