Merge pull request #3940 from CatoTH/OpenSlides-3-ChangeRecommendations-internal

Internal change recommendations
This commit is contained in:
Sean 2018-10-30 10:02:04 +01:00 committed by GitHub
commit eeb29d140b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 140 additions and 7 deletions

View File

@ -8,6 +8,7 @@ export class MotionChangeReco extends BaseModel<MotionChangeReco> {
public id: number;
public motion_id: number;
public rejected: boolean;
public internal: boolean;
public type: number;
public other_description: string;
public line_from: number;

View File

@ -10,6 +10,8 @@
<mat-form-field class="wide-form">
<textarea matInput placeholder='Change recommendation Text' formControlName='text'></textarea>
</mat-form-field>
<mat-checkbox formControlName="public">{{ 'Public' | translate }}</mat-checkbox>
</form>
</mat-dialog-content>
<mat-dialog-actions>

View File

@ -24,7 +24,7 @@ export interface MotionChangeRecommendationComponentData {
* editChangeRecommendation: false,
* newChangeRecommendation: true,
* lineRange: lineRange,
* motion: this.motion,
* changeReco: this.changeRecommendation,
* };
* this.dialogService.open(MotionChangeRecommendationComponent, {
* height: '400px',
@ -104,14 +104,16 @@ export class MotionChangeRecommendationComponent {
public createForm(): void {
this.contentForm = this.formBuilder.group({
text: [this.changeReco.text, Validators.required],
diffType: [this.changeReco.type, Validators.required]
diffType: [this.changeReco.type, Validators.required],
public: [!this.changeReco.internal]
});
}
public saveChangeRecommendation(): void {
this.changeReco.updateChangeReco(
this.contentForm.controls.diffType.value,
this.contentForm.controls.text.value
this.contentForm.controls.text.value,
!this.contentForm.controls.public.value
);
if (this.newReco) {

View File

@ -111,6 +111,10 @@
<span translate>Reject</span>
<mat-icon *ngIf="change.isRejected()" class="active-indicator">done</mat-icon>
</button>
<button type="button" mat-menu-item (click)="setInternal(change, !change.internal)">
<mat-icon>{{ change.internal ? "check_box_outline_blank" : "check_box" }}</mat-icon>
<span translate>Public</span>
</button>
<button type="button" mat-menu-item (click)="deleteChangeRecommendation(change, $event)">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>

View File

@ -172,13 +172,23 @@ export class MotionDetailDiffComponent implements AfterViewInit {
*/
public setAcceptanceValue(change: ViewChangeReco, value: string): void {
if (value === 'accepted') {
this.recoRepo.setAccepted(change).subscribe(() => {}); // Subscribe to trigger HTTP request
this.recoRepo.setAccepted(change).subscribe(); // Subscribe to trigger HTTP request
}
if (value === 'rejected') {
this.recoRepo.setRejected(change).subscribe(() => {}); // Subscribe to trigger HTTP request
this.recoRepo.setRejected(change).subscribe(); // Subscribe to trigger HTTP request
}
}
/**
* Sets if a change recommendation is internal or not
*
* @param {ViewChangeReco} change
* @param {boolean} internal
*/
public setInternal(change: ViewChangeReco, internal: boolean): void {
this.recoRepo.setInternal(change, internal).subscribe(); // Subscribe to trigger HTTP request
}
/**
* Deletes a change recommendation.
* The template has to make sure only to pass change recommendations to this method.
@ -187,7 +197,7 @@ export class MotionDetailDiffComponent implements AfterViewInit {
* @param {MouseEvent} $event
*/
public deleteChangeRecommendation(reco: ViewChangeReco, $event: MouseEvent): void {
this.recoRepo.delete(reco).subscribe(() => {}); // Subscribe to trigger HTTP request
this.recoRepo.delete(reco).subscribe(); // Subscribe to trigger HTTP request
$event.stopPropagation();
$event.preventDefault();
}

View File

@ -36,16 +36,21 @@ export class ViewChangeReco extends BaseViewModel implements ViewUnifiedChange {
// @TODO Is there any need for this function?
}
public updateChangeReco(type: number, text: string): void {
public updateChangeReco(type: number, text: string, internal: boolean): void {
// @TODO HTML sanitazion
this._changeReco.type = type;
this._changeReco.text = text;
this._changeReco.internal = internal;
}
public get rejected(): boolean {
return this._changeReco ? this._changeReco.rejected : null;
}
public get internal(): boolean {
return this._changeReco ? this._changeReco.internal : null;
}
public get type(): number {
return this._changeReco ? this._changeReco.type : ModificationType.TYPE_REPLACEMENT;
}

View File

@ -133,4 +133,18 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
});
return this.dataSend.updateModel(changeReco, HTTPMethod.PATCH) as Observable<MotionChangeReco>;
}
/**
* Sets if a change recommendation is internal (for the administrators) or not.
*
* @param {ViewChangeReco} change
* @param {boolean} internal
*/
public setInternal(change: ViewChangeReco, internal: boolean): Observable<MotionChangeReco> {
const changeReco = change.changeRecommendation;
changeReco.patchValues({
internal: internal
});
return this.dataSend.updateModel(changeReco, HTTPMethod.PATCH) as Observable<MotionChangeReco>;
}
}

View File

@ -88,6 +88,27 @@ class MotionChangeRecommendationAccessPermissions(BaseAccessPermissions):
return MotionChangeRecommendationSerializer
def get_restricted_data(
self,
full_data: List[Dict[str, Any]],
user: Optional[CollectionElement]) -> List[Dict[str, Any]]:
"""
Removes change recommendations if they are internal and the user has
not the can_manage permission. To see change recommendation the user needs
the can_see permission.
"""
# Parse data.
if has_perm(user, 'motions.can_see'):
has_manage_perms = has_perm(user, 'motion.can_manage')
data = []
for full in full_data:
if not full['internal'] or has_manage_perms:
data.append(full)
else:
data = []
return data
class MotionCommentSectionAccessPermissions(BaseAccessPermissions):
"""

View File

@ -0,0 +1,18 @@
# Generated by Django 2.1 on 2018-10-20 18:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('motions', '0013_motion_sorting_and_statute'),
]
operations = [
migrations.AddField(
model_name='motionchangerecommendation',
name='internal',
field=models.BooleanField(default=True),
),
]

View File

@ -738,6 +738,9 @@ class MotionChangeRecommendation(RESTModelMixin, models.Model):
rejected = models.BooleanField(default=False)
"""If true, this change recommendation has been rejected"""
internal = models.BooleanField(default=True)
"""If true, this change recommendation can not be seen by regular users"""
type = models.PositiveIntegerField(default=0)
"""Replacement (0), Insertion (1), Deletion (2), Other (3)"""

View File

@ -286,6 +286,7 @@ class MotionChangeRecommendationSerializer(ModelSerializer):
'id',
'motion',
'rejected',
'internal',
'type',
'other_description',
'line_from',

View File

@ -12,6 +12,7 @@ from openslides.motions.models import (
Category,
Motion,
MotionBlock,
MotionChangeRecommendation,
MotionComment,
MotionCommentSection,
MotionLog,
@ -1235,6 +1236,57 @@ class TestMotionCommentSection(TestCase):
self.assertEqual(MotionCommentSection.objects.count(), 1)
class RetrieveMotionChangeRecommendation(TestCase):
"""
Tests retrieving motion change recommendations.
"""
def setUp(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
motion = Motion(
title='test_title_3kd)K23,c9239mdj2wcG',
text='test_text_f8FLP,gvprC;wovVEwlQ')
motion.save()
self.public_cr = MotionChangeRecommendation(
motion=motion,
internal=False,
line_from=1,
line_to=1)
self.public_cr.save()
self.internal_cr = MotionChangeRecommendation(
motion=motion,
internal=True,
line_from=2,
line_to=2)
self.internal_cr.save()
def test_simple(self):
"""
Test retrieving all change recommendations.
"""
response = self.client.get(reverse('motionchangerecommendation-list'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 2)
def test_non_admin(self):
"""
Test retrieving of all change recommendations that are public, if the user
has no manage perms.
"""
self.admin = get_user_model().objects.get(username='admin')
self.admin.groups.add(GROUP_DELEGATE_PK)
self.admin.groups.remove(GROUP_ADMIN_PK)
inform_changed_data(self.admin)
response = self.client.get(reverse('motionchangerecommendation-list'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]['id'], self.public_cr.id)
class CreateMotionChangeRecommendation(TestCase):
"""
Tests motion change recommendation creation.