From 888ec97cfa26c5b81f1426432a9deaa3076ac237 Mon Sep 17 00:00:00 2001 From: "Mr. Senko" Date: Sat, 30 Dec 2017 11:56:07 +0200 Subject: [PATCH] Use django-attachments for user uploaded files. Fixes #160 - remove tcms/core/files.py - remove now-unused home-grown attachment models - remove related factories and update tests - update templates - update JavaScript files (remove unnecessary parts) - update default permissions As part of this change we no longer copy Plan and Case attachments when cloning these objects. NOTE: Since django-attachments introduces new permission objects you will have to adjust default permissions for existing users. In order for them to be able to upload/delete their own files they need to have `attachments.add_attachment` and `atachments.delete_attachment` permissions. These same permissions are added by default to the 'Tester' group. If you are running an existing installation registering a new user with Kiwi TCMS will update the default permissions for this group! Migrations for 'testcases': - Remove field attachment from testcaseattachment - Remove field case from testcaseattachment - Remove field case_run from testcaseattachment - Remove field attachment from testcase - Delete model TestCaseAttachment Migrations for 'testplans': - Remove field attachment from testplanattachment - Remove field plan from testplanattachment - Remove field attachment from testplan - Delete model TestPlanAttachment Migrations for 'management': - Remove field submitter from testattachment - Remove field attachment from testattachmentdata - Delete model TestAttachment - Delete model TestAttachmentData --- docs/source/modules/tcms.core.files.rst | 7 - docs/source/modules/tcms.core.rst | 1 - requirements/base.txt | 1 + tcms/core/files.py | 237 --------------- tcms/core/tests/test_files.py | 274 ------------------ .../migrations/0003_delete_attachments.py | 29 ++ tcms/management/models.py | 29 -- tcms/settings/common.py | 1 + tcms/static/js/deleconfirm.js | 24 -- tcms/static/js/tcms_actions.js | 1 - tcms/static/js/testcase_actions.js | 5 - tcms/static/js/testplan_actions.js | 28 -- tcms/templates/case/attachment.html | 39 +-- tcms/templates/case/clone.html | 6 - tcms/templates/case/get.html | 3 +- tcms/templates/case/get_attachment.html | 27 +- tcms/templates/case/get_details_case_run.html | 15 +- tcms/templates/plan/attachment.html | 38 +-- tcms/templates/plan/clone.html | 1 - tcms/templates/plan/get.html | 3 +- tcms/templates/plan/get_attachments.html | 21 +- tcms/testcases/forms.py | 6 - .../migrations/0011_delete_attachments.py | 32 ++ tcms/testcases/models.py | 17 -- tcms/testcases/views.py | 14 +- tcms/testplans/forms.py | 5 - .../migrations/0005_delete_attachments.py | 28 ++ tcms/testplans/models.py | 23 -- tcms/testplans/tests.py | 11 - tcms/testplans/views.py | 1 - tcms/tests/factories.py | 55 ---- tcms/urls.py | 8 +- tcms/utils/permissions.py | 7 + tcms/xmlrpc/api/testcase.py | 1 - tcms/xmlrpc/api/testplan.py | 1 - tcms/xmlrpc/serializer.py | 2 +- tcms/xmlrpc/tests/test_serializer.py | 11 +- 37 files changed, 170 insertions(+), 842 deletions(-) delete mode 100644 docs/source/modules/tcms.core.files.rst delete mode 100644 tcms/core/files.py delete mode 100644 tcms/core/tests/test_files.py create mode 100644 tcms/management/migrations/0003_delete_attachments.py delete mode 100644 tcms/static/js/deleconfirm.js create mode 100644 tcms/testcases/migrations/0011_delete_attachments.py create mode 100644 tcms/testplans/migrations/0005_delete_attachments.py diff --git a/docs/source/modules/tcms.core.files.rst b/docs/source/modules/tcms.core.files.rst deleted file mode 100644 index cae88d2b53..0000000000 --- a/docs/source/modules/tcms.core.files.rst +++ /dev/null @@ -1,7 +0,0 @@ -tcms\.core\.files module -======================== - -.. automodule:: tcms.core.files - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/modules/tcms.core.rst b/docs/source/modules/tcms.core.rst index f4e433f438..82cc6ad585 100644 --- a/docs/source/modules/tcms.core.rst +++ b/docs/source/modules/tcms.core.rst @@ -30,7 +30,6 @@ Submodules tcms.core.context_processors tcms.core.db tcms.core.exceptions - tcms.core.files tcms.core.managers tcms.core.middleware tcms.core.responses diff --git a/requirements/base.txt b/requirements/base.txt index 136f524d0d..b37e4a106a 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,5 @@ Django==2.0 +django-attachments django-uuslug odfpy python-bugzilla diff --git a/tcms/core/files.py b/tcms/core/files.py deleted file mode 100644 index 14ed806d17..0000000000 --- a/tcms/core/files.py +++ /dev/null @@ -1,237 +0,0 @@ -# -*- coding: utf-8 -*- - -import os - -from datetime import datetime - -from django.conf import settings -from django.contrib.auth.decorators import permission_required -from django.urls import reverse -from django.views.decorators.http import require_POST -from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.utils.encoding import smart_str -from django.http import JsonResponse - -from tcms.core.views import Prompt -from tcms.management.models import TestAttachment -from tcms.testcases.models import TestCase -from tcms.testcases.models import TestCaseAttachment -from tcms.testplans.models import TestPlan -from tcms.testplans.models import TestPlanAttachment - - -@require_POST -@permission_required('management.add_testattachment') -def upload_file(request): - if 'to_plan_id' not in request.POST and 'to_case_id' not in request.POST: - return Prompt.render( - request=request, - info_type=Prompt.Alert, - info='Uploading file works with plan or case. Nitrate cannot ' - 'proceed with no plan or case ID.', - next='javascript:window.history.go(-1);', - ) - - if request.FILES.get('upload_file'): - upload_file = request.FILES['upload_file'] - - try: - upload_file.name.encode('utf8') - except UnicodeEncodeError: - return Prompt.render( - request=request, - info_type=Prompt.Alert, - info='Upload File name is not legal.', - next='javascript:window.history.go(-1);', - ) - - now = datetime.now() - - stored_name = '%s-%s-%s' % (request.user.username, now, upload_file.name) - - stored_file_name = os.path.join( - settings.MEDIA_ROOT, stored_name).replace('\\', '/') - stored_file_name = smart_str(stored_file_name) - - if upload_file.size > settings.MAX_UPLOAD_SIZE: - return Prompt.render( - request=request, - info_type=Prompt.Alert, - info='You upload entity is too large. Please ensure the file is' - ' less than {} bytes.'.format(settings.MAX_UPLOAD_SIZE), - next='javascript:window.history.go(-1);', - ) - - # Create the upload directory when it's not exist - if not os.path.exists(settings.MEDIA_ROOT): - os.mkdir(settings.MEDIA_ROOT) - - if os.path.exists(stored_file_name): - return Prompt.render( - request=request, - info_type=Prompt.Alert, - info="File named '{}' already exists in upload folder, please " - "rename to another name for solve conflict.".format( - upload_file.name), - next='javascript:window.history.go(-1);', - ) - - dest = open(stored_file_name, 'wb+') - for chunk in upload_file.chunks(): - dest.write(chunk) - dest.close() - - # Write the file to database - # store_file = open(upload_file_name, 'ro') - ta = TestAttachment.objects.create( - submitter_id=request.user.id, - description=request.POST.get('description', None), - file_name=upload_file.name, - stored_name=stored_name, - create_date=now, - mime_type=upload_file.content_type - ) - - if request.POST.get('to_plan_id'): - TestPlanAttachment.objects.create( - plan_id=int(request.POST['to_plan_id']), - attachment_id=ta.attachment_id, - ) - return HttpResponseRedirect( - reverse('plan-attachment', - args=[request.POST['to_plan_id']]) - ) - - if request.POST.get('to_case_id'): - TestCaseAttachment.objects.create( - attachment_id=ta.attachment_id, - case_id=int(request.POST['to_case_id']) - ) - return HttpResponseRedirect( - reverse('testcases-attachment', - args=[request.POST['to_case_id']]) - ) - else: - if 'to_plan_id' in request.POST: - return HttpResponseRedirect( - reverse('plan-attachment', args=[request.POST['to_plan_id']]) - ) - if 'to_case_id' in request.POST: - return HttpResponseRedirect( - reverse('testcases-attachment', - args=[request.POST['to_case_id']]) - ) - - -def check_file(request, file_id): - import os - from urllib import unquote - from django.conf import settings - from tcms.management.models import TestAttachment, TestAttachmentData - - try: - attachment = TestAttachment.objects.get(attachment_id=file_id) - except TestAttachment.DoesNotExist: - raise Http404 - - try: - attachment = TestAttachment.objects.get(attachment_id=file_id) - attachment_data = TestAttachmentData.objects.get( - attachment__attachment_id=file_id - ) - contents = attachment_data.contents - except TestAttachmentData.DoesNotExist: - if attachment.stored_name: - stored_file_name = os.path.join( - settings.MEDIA_ROOT, unquote(attachment.stored_name) - ).replace('\\', '/') - stored_file_name = stored_file_name.encode('utf-8') - try: - f = open(stored_file_name, 'ro') - contents = f.read() - except IOError as error: - raise Http404(error) - else: - stored_file_name = os.path.join( - settings.MEDIA_ROOT, unquote(attachment.file_name) - ).replace('\\', '/') - stored_file_name = stored_file_name.encode('utf-8') - try: - f = open(stored_file_name, 'ro') - contents = f.read() - except IOError as error: - raise Http404(error) - - response = HttpResponse(contents, content_type=str(attachment.mime_type)) - file_name = smart_str(attachment.file_name) - response['Content-Disposition'] = \ - 'attachment; filename="%s"' % file_name - return response - - -def able_to_delete_attachment(request, file_id): - """ - These are allowed to delete attachment - - 1. super user - 2. attachments's submitter - 3. testplan's author or owner - 4. testcase's owner - """ - - user = request.user - if user.is_superuser: - return True - - attach = TestAttachment.objects.get(attachment_id=file_id) - if user.pk == attach.submitter_id: - return True - - if 'from_plan' in request.GET: - plan_id = int(request.GET['from_plan']) - plan = TestPlan.objects.get(plan_id=plan_id) - return user.pk == plan.owner_id or user.pk == plan.author_id - - if 'from_case' in request.GET: - case_id = int(request.GET['from_case']) - case = TestCase.objects.get(case_id=case_id) - return user.pk == case.author_id - - return False - - -# Delete Attachment -def delete_file(request, file_id): - ajax_response = {'rc': 0, 'response': 'ok'} - DELEFAILURE = 1 - AUTHUNSUCCESS = 2 - - state = able_to_delete_attachment(request, file_id) - if not state: - ajax_response['rc'] = AUTHUNSUCCESS - ajax_response['response'] = 'auth_failure' - return JsonResponse(ajax_response) - - # Delete plan's attachment - if 'from_plan' in request.GET: - try: - plan_id = int(request.GET['from_plan']) - attachment = TestPlanAttachment.objects.filter(attachment=file_id, - plan_id=plan_id) - attachment.delete() - except TestPlanAttachment.DoesNotExist: - ajax_response['rc'] = DELEFAILURE - ajax_response['response'] = 'failure' - return JsonResponse(ajax_response) - - # Delete cases' attachment - elif 'from_case' in request.GET: - try: - case_id = int(request.GET['from_case']) - attachment = TestCaseAttachment.objects.filter(attachment=file_id, - case_id=case_id) - attachment.delete() - except TestCaseAttachment.DoesNotExist: - ajax_response['rc'] = DELEFAILURE - ajax_response['response'] = 'failure' - - return JsonResponse(ajax_response) diff --git a/tcms/core/tests/test_files.py b/tcms/core/tests/test_files.py deleted file mode 100644 index 80d7f0e189..0000000000 --- a/tcms/core/tests/test_files.py +++ /dev/null @@ -1,274 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -import shutil -import tempfile - -from mock import patch - -from django.urls import reverse -from django.test import RequestFactory -from django.conf import settings - -from tcms.core.files import able_to_delete_attachment -from tcms.management.models import TestAttachment -from tcms.testcases.models import TestCaseAttachment -from tcms.testplans.models import TestPlanAttachment -from tcms.tests.factories import TestAttachmentFactory -from tcms.tests.factories import TestPlanAttachmentFactory -from tcms.tests.factories import TestCaseAttachmentFactory -from tcms.tests.factories import UserFactory -from tcms.tests import BasePlanCase -from tcms.tests import create_request_user -from tcms.tests import user_should_have_perm - - -class TestUploadFile(BasePlanCase): - """Test view upload_file""" - - @classmethod - def setUpTestData(cls): - super(TestUploadFile, cls).setUpTestData() - - cls.upload_file_url = reverse('mgmt-upload_file') - - cls.password = 'password' - cls.user = create_request_user(username='uploader', password=cls.password) - user_should_have_perm(cls.user, 'management.add_testattachment') - user_should_have_perm(cls.user, 'testcases.add_testcaseattachment') - - @classmethod - def setUpClass(cls): - super(TestUploadFile, cls).setUpClass() - - cls.file_upload_dir = tempfile.mkdtemp( - prefix='{0}-upload-dir'.format(cls.__name__)) - - @classmethod - def tearDownClass(cls): - shutil.rmtree(cls.file_upload_dir) - - super(TestUploadFile, cls).tearDownClass() - - def setUp(self): - super(TestUploadFile, self).setUp() - - fd, self.upload_filename = tempfile.mkstemp( - prefix='{0}-upload-file.txt'.format(self.__class__.__name__), - text=True) - os.write(fd, 'abc'.encode(settings.DEFAULT_CHARSET) * 100) - os.close(fd) - - def tearDown(self): - os.remove(self.upload_filename) - super(TestUploadFile, self).tearDown() - - def test_no_file_is_posted(self): - self.client.login(username=self.user.username, password=self.password) - - response = self.client.post(reverse('mgmt-upload_file'), - {'to_plan_id': self.plan.pk}) - self.assertRedirects( - response, - reverse('plan-attachment', args=[self.plan.pk])) - - response = self.client.post(reverse('mgmt-upload_file'), - {'to_case_id': self.case_1.pk}) - self.assertRedirects( - response, - reverse('testcases-attachment', args=[self.case_1.pk])) - - @patch('tcms.core.files.settings.MAX_UPLOAD_SIZE', new=10) - def test_refuse_if_file_is_too_big(self): - self.client.login(username=self.user.username, password=self.password) - - with open(self.upload_filename, 'r') as upload_file: - response = self.client.post(self.upload_file_url, - {'to_plan_id': self.plan.pk, - 'upload_file': upload_file}) - - self.assertContains(response, 'You upload entity is too large') - - def test_upload_file_to_plan(self): - self.client.login(username=self.user.username, password=self.password) - - with patch('tcms.core.files.settings.MEDIA_ROOT', - new=self.file_upload_dir): - with open(self.upload_filename, 'r') as upload_file: - response = self.client.post(self.upload_file_url, - {'to_plan_id': self.plan.pk, - 'upload_file': upload_file}) - - self.assertRedirects( - response, - reverse('plan-attachment', args=[self.plan.pk])) - - attachments = list(TestAttachment.objects.filter( - file_name=os.path.basename(self.upload_filename))) - self.assertTrue(attachments) - - attachment = attachments[0] - self.assertEqual(self.user.pk, attachment.submitter.pk) - - plan_attachment_rel_exists = TestPlanAttachment.objects.filter( - plan=self.plan, attachment=attachment).exists() - self.assertTrue(plan_attachment_rel_exists) - - def test_upload_file_to_case(self): - self.client.login(username=self.user.username, password=self.password) - - with patch('tcms.core.files.settings.MEDIA_ROOT', - new=self.file_upload_dir): - with open(self.upload_filename, 'r') as upload_file: - response = self.client.post(self.upload_file_url, - {'to_case_id': self.case_1.pk, - 'upload_file': upload_file}) - - self.assertRedirects( - response, - reverse('testcases-attachment', args=[self.case_1.pk])) - - attachments = list(TestAttachment.objects.filter( - file_name=os.path.basename(self.upload_filename))) - self.assertTrue(attachments) - - attachment = attachments[0] - self.assertEqual(self.user.pk, attachment.submitter.pk) - - case_attachment_rel_exists = TestCaseAttachment.objects.filter( - case=self.case_1, attachment=attachment).exists() - self.assertTrue(case_attachment_rel_exists) - - -class TestAbleToDeleteFile(BasePlanCase): - - @classmethod - def setUpClass(cls): - super(TestAbleToDeleteFile, cls).setUpClass() - - cls.superuser = UserFactory(username='admin') - cls.superuser.is_superuser = True - cls.superuser.set_password('admin') - cls.superuser.save() - - cls.anyone_else = UserFactory() - - cls.attachment = TestAttachmentFactory() - - def setUp(self): - super(TestAbleToDeleteFile, self).setUp() - - self.fake_file_id = self.attachment.pk - self.request = RequestFactory() - - def test_superuser_can(self): - request = self.request.get(reverse('mgmt-delete_file', - args=[self.fake_file_id])) - request.user = self.superuser - self.assertTrue(able_to_delete_attachment(request, self.fake_file_id)) - - def test_attachment_submitter_can(self): - request = self.request.get(reverse('mgmt-delete_file', - args=[self.fake_file_id])) - request.user = self.attachment.submitter - self.assertTrue(able_to_delete_attachment(request, self.fake_file_id)) - - def test_plan_author_can(self): - request = self.request.get(reverse('mgmt-delete_file', - args=[self.fake_file_id]), - data={'from_plan': self.plan.pk}) - request.user = self.plan.author - self.assertTrue(able_to_delete_attachment(request, self.fake_file_id)) - - def test_plan_owner_can(self): - request = self.request.get(reverse('mgmt-delete_file', - args=[self.fake_file_id]), - data={'from_plan': self.plan.pk}) - request.user = self.plan.owner - self.assertTrue(able_to_delete_attachment(request, self.fake_file_id)) - - def test_case_owner_can(self): - request = self.request.get(reverse('mgmt-delete_file', - args=[self.fake_file_id]), - data={'from_case': self.case_1.pk}) - request.user = self.case_1.author - self.assertTrue(able_to_delete_attachment(request, self.fake_file_id)) - - def test_cannot_delete_by_others(self): - request = self.request.get(reverse('mgmt-delete_file', - args=[self.fake_file_id]), - data={'from_case': self.case_1.pk}) - request.user = self.anyone_else - self.assertFalse(able_to_delete_attachment(request, self.fake_file_id)) - - -class TestDeleteFileAuthorization(BasePlanCase): - - @classmethod - def setUpClass(cls): - super(TestDeleteFileAuthorization, cls).setUpClass() - - cls.superuser = UserFactory(username='admin') - cls.superuser.set_password('admin') - cls.superuser.save() - - cls.anyone_else = UserFactory() - cls.anyone_else_pwd = 'anyone' - cls.anyone_else.set_password(cls.anyone_else_pwd) - cls.anyone_else.save() - - cls.plan_attachment = TestAttachmentFactory() - cls.plan_attachment_rel = TestPlanAttachmentFactory( - plan=cls.plan, - attachment=cls.plan_attachment) - cls.submitter_pwd = 'secret' - cls.plan_attachment.submitter.set_password(cls.submitter_pwd) - cls.plan_attachment.submitter.save() - - cls.case_attachment = TestAttachmentFactory() - cls.case_attachment_rel = TestCaseAttachmentFactory( - case=cls.case_1, - attachment=cls.case_attachment) - cls.case_attachment.submitter.set_password(cls.submitter_pwd) - cls.case_attachment.submitter.save() - - def test_refuse_if_user_cannot_delete_file(self): - self.client.login(username=self.anyone_else.username, - password=self.anyone_else_pwd) - - url = reverse('mgmt-delete_file', args=[self.plan_attachment.pk]) - response = self.client.get(url, {'from_plan': self.plan.pk}) - - self.assertJSONEqual( - str(response.content, encoding=settings.DEFAULT_CHARSET), - {'rc': 2, 'response': 'auth_failure'}) - - def test_delete_attachment_from_plan(self): - self.client.login(username=self.plan_attachment.submitter.username, - password=self.submitter_pwd) - - url = reverse('mgmt-delete_file', args=[self.plan_attachment.pk]) - response = self.client.get(url, {'from_plan': self.plan.pk}) - - self.assertJSONEqual( - str(response.content, encoding=settings.DEFAULT_CHARSET), - {'rc': 0, 'response': 'ok'}) - still_has = self.plan.attachment.filter(pk=self.plan_attachment.pk).exists() - self.assertFalse(still_has) - # TODO: skip because delete_file does not delete a TestAttachment object from database - # self.assertFalse(TestAttachment.objects.filter(pk=self.plan_attachment.pk).exists()) - - def test_delete_attachment_from_case(self): - self.client.login(username=self.case_attachment.submitter.username, - password=self.submitter_pwd) - - url = reverse('mgmt-delete_file', args=[self.case_attachment.pk]) - response = self.client.get(url, {'from_case': self.case_1.pk}) - - self.assertJSONEqual( - str(response.content, encoding=settings.DEFAULT_CHARSET), - {'rc': 0, 'response': 'ok'}) - still_has = self.case_1.attachment.filter(pk=self.case_attachment.pk).exists() - self.assertFalse(still_has) - # TODO: skip because delete_file does not delete a TestAttachment object from database - # self.assertFalse(TestAttachment.objects.filter(pk=self.case_attachment.pk).exists()) diff --git a/tcms/management/migrations/0003_delete_attachments.py b/tcms/management/migrations/0003_delete_attachments.py new file mode 100644 index 0000000000..64214d5e8f --- /dev/null +++ b/tcms/management/migrations/0003_delete_attachments.py @@ -0,0 +1,29 @@ +# Generated by Django 2.0 on 2017-12-30 10:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('testcases', '0011_delete_attachments'), + ('testplans', '0005_delete_attachments'), + ('management', '0002_add_initial_data'), + ] + + operations = [ + migrations.RemoveField( + model_name='testattachment', + name='submitter', + ), + migrations.RemoveField( + model_name='testattachmentdata', + name='attachment', + ), + migrations.DeleteModel( + name='TestAttachment', + ), + migrations.DeleteModel( + name='TestAttachmentData', + ), + ] diff --git a/tcms/management/models.py b/tcms/management/models.py index c73769b142..5cf7cf72aa 100755 --- a/tcms/management/models.py +++ b/tcms/management/models.py @@ -3,7 +3,6 @@ from django.core.cache import cache from django.db import models -from tcms.core.models import BlobField from tcms.core.models import TCMSActionModel from tcms.core.utils import calc_percent @@ -388,34 +387,6 @@ def get_or_create_many_by_name(cls, names): tags.append(new_tag) return tags - -# Test attachements file zone - - -class TestAttachment(models.Model): - attachment_id = models.AutoField(max_length=10, primary_key=True) - submitter = models.ForeignKey('auth.User', related_name='attachments', blank=True, null=True, - on_delete=models.CASCADE) - description = models.CharField(max_length=1024, blank=True, null=True) - file_name = models.CharField(db_column='filename', max_length=255, unique=True, blank=True) - stored_name = models.CharField(max_length=128, unique=True, blank=True, null=True) - create_date = models.DateTimeField(db_column='creation_ts') - mime_type = models.CharField(max_length=100) - - def __str__(self): - return self.file_name - - class Meta: - db_table = u'test_attachments' - - -class TestAttachmentData(models.Model): - attachment = models.ForeignKey(TestAttachment, on_delete=models.CASCADE) - contents = BlobField(blank=True) - - class Meta: - db_table = u'test_attachment_data' - # ============================ # New TCMS Environments models # ============================ diff --git a/tcms/settings/common.py b/tcms/settings/common.py index 323da3037b..ef0e653d8c 100644 --- a/tcms/settings/common.py +++ b/tcms/settings/common.py @@ -219,6 +219,7 @@ 'django.contrib.sites', 'django.contrib.staticfiles', + 'attachments', 'django_comments', 'dj_pagination', 'modernrpc', diff --git a/tcms/static/js/deleconfirm.js b/tcms/static/js/deleconfirm.js deleted file mode 100644 index 9b0325523a..0000000000 --- a/tcms/static/js/deleconfirm.js +++ /dev/null @@ -1,24 +0,0 @@ -function deleConfirm(attachment_id, home, plan_id) { - var url = "/management/deletefile/" + attachment_id + "?" + home + "=" + plan_id; - var answer = window.confirm("Arey you sure to delete the attachment?"); - if (!answer) { - return false; - } - - jQ.ajax({ - 'url': url, - 'type': 'GET', - 'success': function(data, textStatus, jqXHR) { - var returnobj = jQ.parseJSON(jqXHR.responseText); - if (returnobj.rc === 0) { - jQ('#' + attachment_id).remove(); - jQ('#attachment_count').text(parseInt(jQ('#attachment_count').text()) - 1); - } else if (returnobj.response === 'auth_failure') { - window.alert('Permission denied!'); - } else { - window.alert('Server Exception'); - } - } - }); -} - diff --git a/tcms/static/js/tcms_actions.js b/tcms/static/js/tcms_actions.js index dca81a5d34..16ee495e0e 100644 --- a/tcms/static/js/tcms_actions.js +++ b/tcms/static/js/tcms_actions.js @@ -164,7 +164,6 @@ var default_messages = { get_form: '/ajax/form/', get_product_info: '/management/getinfo/', - upload_file: '/management/uploadfile/', modify_plan : '/plan/$id/modify/', plan_assign_case: '/plan/$id/assigncase/apply/', diff --git a/tcms/static/js/testcase_actions.js b/tcms/static/js/testcase_actions.js index 91b07a6a43..03a55cf364 100644 --- a/tcms/static/js/testcase_actions.js +++ b/tcms/static/js/testcase_actions.js @@ -345,11 +345,6 @@ Nitrate.TestCases.Details.on_load = function() { ] }); - jQ('.js-del-button').bind('click', function(event) { - var params = jQ(event.target).data('params'); - deleConfirm(params.attachmentId, params.source, params.sourceId); - }); - jQ('.js-remove-button').bind('click', function(event) { var params = jQ(event.target).data('params'); removeCaseBug(params.id, params.caseId, params.caseRunId); diff --git a/tcms/static/js/testplan_actions.js b/tcms/static/js/testplan_actions.js index 548997bec7..71847e2777 100644 --- a/tcms/static/js/testplan_actions.js +++ b/tcms/static/js/testplan_actions.js @@ -6,7 +6,6 @@ Nitrate.TestPlans.Details = {}; Nitrate.TestPlans.Edit = {}; Nitrate.TestPlans.SearchCase = {}; Nitrate.TestPlans.Clone = {}; -Nitrate.TestPlans.Attachment = {}; /* * Hold container IDs @@ -1016,10 +1015,6 @@ Nitrate.TestPlans.Details = { var params = jQ(this).data('params'); window.location.href = params[0] + '?plan=' + params[1]; }); - jQ('.js-del-attach').bind('click', function() { - var params = jQ(this).data('params'); - deleConfirm(params[0], 'from_plan', params[1]); - }); jQ('#js-update-components').live('click', function() { constructPlanComponentModificationDialog(); }); @@ -1130,29 +1125,6 @@ Nitrate.TestPlans.Clone.on_load = function() { }); }; -Nitrate.TestPlans.Attachment.on_load = function() { - jQ(document).ready(function() { - jQ("#upload_file").change(function () { - var iSize = jQ("#upload_file")[0].files[0].size; - var limit = parseInt(jQ('#upload_file').attr('limit')); - - if (iSize > limit) { - window.alert("Your attachment's size is beyond limit, please limit your attachments to under 5 megabytes (MB)."); - } - }); - - jQ('.js-back-button').bind('click', function() { - window.history.go(-1); - }); - - jQ('.js-del-attach').bind('click', function() { - var params = jQ(this).data('params'); - deleConfirm(params[0], params[1], params[2]); - }); - - }); -}; - function showMoreSummary() { jQ('#display_summary').show(); if (jQ('#display_summary_short').length) { diff --git a/tcms/templates/case/attachment.html b/tcms/templates/case/attachment.html index 49ed27172c..f2e70237cd 100644 --- a/tcms/templates/case/attachment.html +++ b/tcms/templates/case/attachment.html @@ -1,5 +1,7 @@ {% extends "tcms_base.html" %} {% load static %} +{% load attachments_tags %} + {% block subtitle %}{{ testplan.title }}{% endblock %} {% block custom_stylesheet %} @@ -13,10 +15,6 @@ {% block custom_javascript %} - - {% endblock %} {% block contents %} @@ -34,38 +32,31 @@

{{ testcase.summary }}

-
-
- Upload New Attachment - - - - - + {% attachment_form testcase %} + + Note: Each attachment size limit is {{ limit_readable }}. -
-
+ - + - - {% for attachment in testcase.attachment.all %} - - - - - - + {% get_attachments_for testcase as attachments_list %} + {% for attachment in attachments_list %} + + + + + - {% endfor %} + {% endfor %}
File nameSubmitterSubmitter Create dateMimetype Action
{{ attachment.file_name }}{{ attachment.submitter }}{{ attachment.create_date }}{{ attachment.mime_type }}View | Delete
{{ attachment.filename }}{{ attachment.creator }}{{ attachment.created }}{% attachment_delete_link attachment %}
diff --git a/tcms/templates/case/clone.html b/tcms/templates/case/clone.html index 8f4bff53f6..700529c925 100644 --- a/tcms/templates/case/clone.html +++ b/tcms/templates/case/clone.html @@ -95,12 +95,6 @@

Clone Test Case(s)

-
  • - {{ clone_form.copy_attachment }} - - - -
  • diff --git a/tcms/templates/case/get.html b/tcms/templates/case/get.html index 0780257aa3..eae924775d 100644 --- a/tcms/templates/case/get.html +++ b/tcms/templates/case/get.html @@ -11,7 +11,6 @@ - - - {% endblock %} {% block contents %} @@ -33,39 +30,30 @@

    {{ test_plan.name }}

    -
    -
    - Upload New Attachment - - - - - + {% attachment_form test_plan %} Note: Each attachment size limit is {{ limit_readable }}. -
    -
    + - + - - {% for attachment in test_plan.attachment.all %} - - - - - - - - {% endfor %} + {% get_attachments_for test_plan as attachments_list %} + {% for attachment in attachments_list %} + + + + + + + {% endfor %}
    {{ attachment.file_name }}{{ attachment.submitter }}{{ attachment.create_date }}{{ attachment.mime_type }}View | Delete
    {{ attachment.filename }}{{ attachment.creator }}{{ attachment.created }}{% attachment_delete_link attachment %}
    diff --git a/tcms/templates/plan/clone.html b/tcms/templates/plan/clone.html index ed6a6017b3..0be3d71432 100644 --- a/tcms/templates/plan/clone.html +++ b/tcms/templates/plan/clone.html @@ -80,7 +80,6 @@

    Clone Test Plan

  • {{ clone_form.set_parent }}{{ clone_form.set_parent.label }} ({{ clone_form.set_parent.help_text }})
  • {{ clone_form.keep_orignal_author }}{{ clone_form.keep_orignal_author.label }} ({{ clone_form.keep_orignal_author.help_text }})
  • {{ clone_form.copy_texts }}{{ clone_form.copy_texts.label }} ({{ clone_form.copy_texts.help_text }})
  • -
  • {{ clone_form.copy_attachements }}{{ clone_form.copy_attachements.label }} ({{ clone_form.copy_attachements.help_text }})
  • {{ clone_form.copy_environment_group }}{{ clone_form.copy_environment_group.label }} ({{ clone_form.copy_environment_group.help_text }})
  • diff --git a/tcms/templates/plan/get.html b/tcms/templates/plan/get.html index 1960a9de84..bc609ade62 100644 --- a/tcms/templates/plan/get.html +++ b/tcms/templates/plan/get.html @@ -13,7 +13,6 @@ - @@ -178,7 +177,7 @@

    Default Components ({{ test_plan.component.count }})

  • - Attachments ({{ test_plan.attachment.count }}) + Attachments (??)
  • Tags ({{ test_plan.tag.count }}) diff --git a/tcms/templates/plan/get_attachments.html b/tcms/templates/plan/get_attachments.html index ec7da312ad..305df9cb08 100644 --- a/tcms/templates/plan/get_attachments.html +++ b/tcms/templates/plan/get_attachments.html @@ -1,6 +1,8 @@ +{% load attachments_tags %} +
    + {% if perms.attachments.add_attachment %} Add attachment - {% if perms.management.add_testattachment %} add {% endif %}
    @@ -8,20 +10,19 @@ File name - Submitter + Submitter Create date - Mimetype Action - {% for attachment in test_plan.attachment.all %} - - {{ attachment.file_name }} - {{ attachment.submitter }} - {{ attachment.create_date }} - {{ attachment.mime_type }} - View | Delete + {% get_attachments_for test_plan as attachments_list %} + {% for attachment in attachments_list %} + + {{ attachment.filename }} + {{ attachment.creator }} + {{ attachment.created }} + {% attachment_delete_link attachment %} {% empty %} No attachments diff --git a/tcms/testcases/forms.py b/tcms/testcases/forms.py index ef86aa02c4..025aa4b5b9 100644 --- a/tcms/testcases/forms.py +++ b/tcms/testcases/forms.py @@ -493,12 +493,6 @@ class CloneCaseForm(forms.Form): 'Unchecking will remove components from copied test case)', required=False ) - copy_attachment = forms.BooleanField( - label='Copy the attachments', - help_text='Copy test case attachments (' - 'Unchecking will remove attachments of copied test case)', - required=False - ) def populate(self, case_ids, plan=None): self.fields['case'].queryset = TestCase.objects.filter( diff --git a/tcms/testcases/migrations/0011_delete_attachments.py b/tcms/testcases/migrations/0011_delete_attachments.py new file mode 100644 index 0000000000..fbab079d94 --- /dev/null +++ b/tcms/testcases/migrations/0011_delete_attachments.py @@ -0,0 +1,32 @@ +# Generated by Django 2.0 on 2017-12-30 10:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('testcases', '0010_update_blank_and_null_constraints'), + ] + + operations = [ + migrations.RemoveField( + model_name='testcaseattachment', + name='attachment', + ), + migrations.RemoveField( + model_name='testcaseattachment', + name='case', + ), + migrations.RemoveField( + model_name='testcaseattachment', + name='case_run', + ), + migrations.RemoveField( + model_name='testcase', + name='attachment', + ), + migrations.DeleteModel( + name='TestCaseAttachment', + ), + ] diff --git a/tcms/testcases/models.py b/tcms/testcases/models.py index 12f4dac06e..7a81168778 100644 --- a/tcms/testcases/models.py +++ b/tcms/testcases/models.py @@ -144,9 +144,6 @@ class TestCase(TCMSActionModel): null=True, on_delete=models.CASCADE) - attachment = models.ManyToManyField('management.TestAttachment', related_name='cases', - through='testcases.TestCaseAttachment') - # FIXME: related_name should be cases instead of case. But now keep it # named case due to historical reason. plan = models.ManyToManyField('testplans.TestPlan', related_name='case', @@ -595,20 +592,6 @@ class Meta: unique_together = ('plan', 'case') -class TestCaseAttachment(models.Model): - attachment = models.ForeignKey('management.TestAttachment', on_delete=models.CASCADE) - case = models.ForeignKey(TestCase, default=None, related_name='case_attachment', - on_delete=models.CASCADE) - case_run = models.ForeignKey('testruns.TestCaseRun', default=None, - null=True, blank=True, - related_name='case_run_attachment', - on_delete=models.CASCADE) - - class Meta: - db_table = u'test_case_attachments' - # FIXME: what unique constraints are needed against this model? - - class TestCaseComponent(models.Model): case = models.ForeignKey(TestCase, on_delete=models.CASCADE) # case_id component = models.ForeignKey('management.Component', on_delete=models.CASCADE) # component_id diff --git a/tcms/testcases/views.py b/tcms/testcases/views.py index ee53fa0595..5d37fa30b8 100644 --- a/tcms/testcases/views.py +++ b/tcms/testcases/views.py @@ -31,7 +31,7 @@ from tcms.testcases import actions from tcms.testcases import data from tcms.testcases.models import TestCase, TestCaseStatus, \ - TestCaseAttachment, TestCasePlan, TestCaseBugSystem, \ + TestCasePlan, TestCaseBugSystem, \ TestCaseBug, TestCaseText, TestCaseComponent from tcms.management.models import Priority, TestTag from tcms.testplans.models import TestPlan @@ -752,7 +752,6 @@ def get_context_data(self, **kwargs): if case is not None: data.update({ 'test_case_text': case.get_text_with_version(), - 'attachments': case.attachment.only('file_name'), 'components': case.component.only('name'), 'tags': case.tag.only('name'), 'case_comments': self.get_case_comments(case), @@ -903,8 +902,7 @@ def get_context_data(self, **kwargs): try: qs = TestCase.objects.filter(pk=self.case_id) - qs = qs.prefetch_related('attachment', - 'component', + qs = qs.prefetch_related('component', 'tag').only('pk') case = qs[0] @@ -1477,13 +1475,6 @@ def clone(request, template_name='case/clone.html'): for tag in tc_src.tag.all(): tc_dest.add_tag(tag=tag) - - if clone_form.cleaned_data['copy_attachment']: - for attachment in tc_src.attachment.all(): - TestCaseAttachment.objects.create( - case=tc_dest, - attachment=attachment, - ) else: tc_dest = tc_src tc_dest.author = \ @@ -1587,7 +1578,6 @@ def clone(request, template_name='case/clone.html'): 'maintain_case_orignal_author': False, 'maintain_case_orignal_default_tester': False, 'copy_component': True, - 'copy_attachment': True, }) clone_form.populate(case_ids=selected_cases) diff --git a/tcms/testplans/forms.py b/tcms/testplans/forms.py index 6a1cfc07bc..e913e4b007 100644 --- a/tcms/testplans/forms.py +++ b/tcms/testplans/forms.py @@ -347,11 +347,6 @@ class ClonePlanForm(BasePlanForm): help_text='Check it to copy texts of the plan.', required=False, ) - copy_attachements = forms.BooleanField( - label='Copy Plan Attachments', - help_text='Check it to copy attachments of the plan.', - required=False - ) copy_environment_group = forms.BooleanField( label='Copy environment group', help_text='Check it on to copy environment group of the plan.', diff --git a/tcms/testplans/migrations/0005_delete_attachments.py b/tcms/testplans/migrations/0005_delete_attachments.py new file mode 100644 index 0000000000..4ac366e360 --- /dev/null +++ b/tcms/testplans/migrations/0005_delete_attachments.py @@ -0,0 +1,28 @@ +# Generated by Django 2.0 on 2017-12-30 10:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('testplans', '0004_remove_model_TestPlanActivity'), + ] + + operations = [ + migrations.RemoveField( + model_name='testplanattachment', + name='attachment', + ), + migrations.RemoveField( + model_name='testplanattachment', + name='plan', + ), + migrations.RemoveField( + model_name='testplan', + name='attachment', + ), + migrations.DeleteModel( + name='TestPlanAttachment', + ), + ] diff --git a/tcms/testplans/models.py b/tcms/testplans/models.py index fe2303d235..b4d1aa1e88 100644 --- a/tcms/testplans/models.py +++ b/tcms/testplans/models.py @@ -54,8 +54,6 @@ class TestPlan(TCMSActionModel): parent = models.ForeignKey('self', blank=True, null=True, related_name='child_set', on_delete=models.CASCADE) - attachment = models.ManyToManyField('management.TestAttachment', - through='testplans.TestPlanAttachment') component = models.ManyToManyField('management.Component', through='testplans.TestPlanComponent') env_group = models.ManyToManyField('management.TCMSEnvGroup', through='TCMSEnvPlanMap') @@ -193,12 +191,6 @@ def add_env_group(self, env_group): group=env_group, ) - def add_attachment(self, attachment): - return TestPlanAttachment.objects.create( - plan=self, - attachment=attachment, - ) - def add_tag(self, tag): return TestPlanTag.objects.get_or_create( plan=self, @@ -255,7 +247,6 @@ def make_cloned_name(self): def clone(self, new_name=None, product=None, version=None, new_original_author=None, set_parent=True, copy_texts=True, default_text_author=None, - copy_attachments=True, copy_environment_group=True, link_cases=True, copy_cases=None, new_case_author=None, @@ -275,7 +266,6 @@ def clone(self, new_name=None, product=None, version=None, :param bool copy_texts: Whether to copy the four text. Copy by default. :param default_text_author: When not copy the four text, new text will be created. This is the default author of new created text. - :param bool copy_attachments: Whether to copy attachments. Copy by default. :param bool copy_environment_group: Whether to copy environment groups. Copy by default. :param bool link_cases: Whether to link cases to cloned plan. Default is True. :param bool copy_cases: Whether to copy cases to cloned plan instead of just linking them. @@ -320,11 +310,6 @@ def clone(self, new_name=None, product=None, version=None, for tp_tag_src in self.tag.all(): tp_dest.add_tag(tag=tp_tag_src) - # Copy the plan attachments - if copy_attachments: - for tp_attach_src in self.attachment.all(): - tp_dest.add_attachment(attachment=tp_attach_src) - # Copy the environment group if copy_environment_group: for env_group in self.env_group.all(): @@ -407,14 +392,6 @@ class Meta: unique_together = ('plan', 'plan_text_version') -class TestPlanAttachment(models.Model): - attachment = models.ForeignKey('management.TestAttachment', on_delete=models.CASCADE) - plan = models.ForeignKey(TestPlan, on_delete=models.CASCADE) - - class Meta: - db_table = u'test_plan_attachments' - - class TestPlanTag(models.Model): tag = models.ForeignKey('management.TestTag', on_delete=models.CASCADE) plan = models.ForeignKey(TestPlan, on_delete=models.CASCADE) diff --git a/tcms/testplans/tests.py b/tcms/testplans/tests.py index 193fb66968..92403190ed 100644 --- a/tcms/testplans/tests.py +++ b/tcms/testplans/tests.py @@ -18,7 +18,6 @@ from tcms.testcases.models import TestCasePlan from tcms.testplans.models import TCMSEnvPlanMap from tcms.testplans.models import TestPlan -from tcms.testplans.models import TestPlanAttachment from tcms.core.contrib.auth.backends import initiate_user_with_default_setups from tcms.tests.factories import ComponentFactory @@ -679,12 +678,6 @@ def verify_cloned_plan(self, original_plan, cloned_plan, self.assertEqual(copied_text.create_date, original_text.create_date) self.assertEqual(copied_text.plan_text, original_text.plan_text) - # Verify option copy_attachments - for attachment in original_plan.attachment.all(): - added = TestPlanAttachment.objects.filter( - plan=cloned_plan, attachment=attachment).exists() - self.assertTrue(added) - # Verify option copy_environment_groups for env_group in original_plan.env_group.all(): added = TCMSEnvPlanMap.objects.filter(plan=cloned_plan, group=env_group).exists() @@ -728,7 +721,6 @@ def test_clone_a_plan_with_default_options(self): 'product_version': self.version.pk, 'set_parent': 'on', 'copy_texts': 'on', - 'copy_attachments': 'on', 'copy_environment_groups': 'on', 'link_testcases': 'on', 'maintain_case_orignal_author': 'on', @@ -755,7 +747,6 @@ def test_clone_a_plan_by_copying_cases(self): 'product_version': self.version.pk, 'set_parent': 'on', 'copy_texts': 'on', - 'copy_attachments': 'on', 'copy_environment_groups': 'on', 'link_testcases': 'on', 'maintain_case_orignal_author': 'on', @@ -780,7 +771,6 @@ def test_clone_a_plan_by_setting_me_to_copied_cases_author_default_tester(self): 'product_version': self.version.pk, 'set_parent': 'on', 'copy_texts': 'on', - 'copy_attachments': 'on', 'copy_environment_groups': 'on', 'link_testcases': 'on', 'submit': 'Clone', @@ -800,7 +790,6 @@ def test_clone_multiple_plans_with_default_options(self): 'product_version': self.version.pk, 'set_parent': 'on', 'copy_texts': 'on', - 'copy_attachments': 'on', 'copy_environment_groups': 'on', 'link_testcases': 'on', 'maintain_case_orignal_author': 'on', diff --git a/tcms/testplans/views.py b/tcms/testplans/views.py index 5af84ffc58..88c4812776 100644 --- a/tcms/testplans/views.py +++ b/tcms/testplans/views.py @@ -678,7 +678,6 @@ def clone(request, template_name='plan/clone.html'): # Related data copy_texts=clone_options['copy_texts'], - copy_attachments=clone_options['copy_attachements'], copy_environment_group=clone_options['copy_environment_group'], # Link or copy cases diff --git a/tcms/tests/factories.py b/tcms/tests/factories.py index 575689fd8b..a195486461 100644 --- a/tcms/tests/factories.py +++ b/tcms/tests/factories.py @@ -169,26 +169,6 @@ class Meta: name = factory.Sequence(lambda n: 'Tag %d' % n) -class TestAttachmentFactory(DjangoModelFactory): - - class Meta: - model = 'management.TestAttachment' - - file_name = factory.LazyFunction(lambda: '%s.png' % str(datetime.now())) - submitter = factory.SubFactory(UserFactory) - create_date = factory.LazyFunction(datetime.now) - description = factory.Sequence(lambda n: 'Attachment Image %d' % n) - - -class TestAttachmentDataFactory(DjangoModelFactory): - - class Meta: - model = 'management.TestAttachmentData' - - contents = factory.Sequence(lambda n: 'content %d' % n) - attachment = factory.SubFactory(TestAttachmentFactory) - - class TCMSEnvGroupFactory(DjangoModelFactory): class Meta: @@ -258,14 +238,6 @@ class Meta: type = factory.SubFactory(TestPlanTypeFactory) # FIXME: How to create field for field parent - @factory.post_generation - def attachment(self, create, extracted, **kwargs): - if not create: - return - if extracted: - for attachment in extracted: - TestPlanAttachmentFactory(plan=self, attachment=attachment) - @factory.post_generation def component(self, create, extracted, **kwargs): if not create: @@ -291,15 +263,6 @@ def tag(self, create, extracted, **kwargs): TestPlanTagFactory(plan=self, tag=tag) -class TestPlanAttachmentFactory(DjangoModelFactory): - - class Meta: - model = 'testplans.TestPlanAttachment' - - plan = factory.SubFactory(TestPlanFactory) - attachment = factory.SubFactory(TestAttachmentFactory) - - class TestPlanTagFactory(DjangoModelFactory): class Meta: @@ -374,14 +337,6 @@ class Meta: default_tester = factory.SubFactory(UserFactory) reviewer = factory.SubFactory(UserFactory) - @factory.post_generation - def attachment(self, create, extracted, **kwargs): - if not create: - return - if extracted: - for attachment in extracted: - TestCaseAttachmentFactory(case=self, attachment=attachment) - @factory.post_generation def plan(self, create, extracted, **kwargs): if not create: @@ -417,16 +372,6 @@ class Meta: sortkey = factory.Sequence(lambda n: n) -class TestCaseAttachmentFactory(DjangoModelFactory): - - class Meta: - model = 'testcases.TestCaseAttachment' - - attachment = factory.SubFactory(TestAttachmentFactory) - case = factory.SubFactory(TestCaseFactory) - case_run = factory.SubFactory('tcms.tests.factories.TestCaseRunFactory') - - class TestCaseComponentFactory(DjangoModelFactory): class Meta: diff --git a/tcms/urls.py b/tcms/urls.py index 57e9dfde0c..9155befcd4 100644 --- a/tcms/urls.py +++ b/tcms/urls.py @@ -7,12 +7,12 @@ from django.contrib.admindocs import urls as admindocs_urls from django.views.i18n import JavaScriptCatalog +from attachments import urls as attachments_urls from modernrpc.core import JSONRPC_PROTOCOL from modernrpc.core import XMLRPC_PROTOCOL from modernrpc.views import RPCEntryPoint from tinymce import urls as tinymce_urls from tcms.core import ajax -from tcms.core import files from tcms.core import views as core_views from tcms.core.contrib.comments import views as comments_views from tcms.core.contrib.linkreference import views as linkreference_views @@ -33,6 +33,7 @@ url(r'^admin/', admin.site.urls), url(r'^admin/doc/', include(admindocs_urls)), + url(r'^attachments/', include(attachments_urls, namespace='attachments')), url(r'^tinymce/', include(tinymce_urls)), # Index and static zone @@ -56,11 +57,6 @@ url(r'^management/getinfo/$', ajax.info, name='ajax-info'), url(r'^management/tags/$', ajax.tag), - # Attached file zone - url(r'^management/uploadfile/$', files.upload_file, name='mgmt-upload_file'), - url(r'^management/checkfile/(?P\d+)/$', files.check_file, name='mgmt-check_file'), - url(r'^management/deletefile/(?P\d+)/$', files.delete_file, name='mgmt-delete_file'), - # comments url(r'^comments/post/', comments_views.post, name='comments-post'), url(r'^comments/delete/', comments_views.delete, name='comments-delete'), diff --git a/tcms/utils/permissions.py b/tcms/utils/permissions.py index eca16d6353..e842d9256a 100644 --- a/tcms/utils/permissions.py +++ b/tcms/utils/permissions.py @@ -18,3 +18,10 @@ def assign_default_group_permissions(): for app_name in ['django_comments', 'management', 'testcases', 'testplans', 'testruns']: app_perms = Permission.objects.filter(content_type__app_label__contains=app_name) tester.permissions.add(*app_perms) + + # this app was introduced later and we don't want all of its permissions + if tester.permissions.filter(content_type__app_label='attachments').count() == 0: + attachment_perms = Permission.objects.filter( + content_type__app_label='attachments' + ).exclude(codename='delete_foreign_attachments') + tester.permissions.add(*attachment_perms) diff --git a/tcms/xmlrpc/api/testcase.py b/tcms/xmlrpc/api/testcase.py index 0befcc5a62..1bc84c6f95 100644 --- a/tcms/xmlrpc/api/testcase.py +++ b/tcms/xmlrpc/api/testcase.py @@ -493,7 +493,6 @@ def filter(query): +------------------------------------------------------------------+ | Key | Valid Values | | author | A bugzilla login (email address) | - | attachment | ForeignKey: Attchment | | alias | String | | case_id | Integer | | case_status | ForeignKey: Case Stat | diff --git a/tcms/xmlrpc/api/testplan.py b/tcms/xmlrpc/api/testplan.py index 400fb5a78e..27a9e4ab63 100644 --- a/tcms/xmlrpc/api/testplan.py +++ b/tcms/xmlrpc/api/testplan.py @@ -207,7 +207,6 @@ def filter(values={}): +----------------------------------------------------------+ | Key | Valid Values | | author | ForeignKey: Auth.User | - | attachment | ForeignKey: Attachment | | case | ForeignKey: Test Case | | create_date | DateTime | | env_group | ForeignKey: Environment Group | diff --git a/tcms/xmlrpc/serializer.py b/tcms/xmlrpc/serializer.py index a2ded96e09..3338dfbd89 100644 --- a/tcms/xmlrpc/serializer.py +++ b/tcms/xmlrpc/serializer.py @@ -404,7 +404,7 @@ class TestPlanXMLRPCSerializer(QuerySetBasedXMLRPCSerializer): 'alias': {'product_version': 'default_product_version'}, } - m2m_fields = ('attachment', 'case', 'component', 'env_group', 'tag') + m2m_fields = ('case', 'component', 'env_group', 'tag') class TestCaseRunXMLRPCSerializer(QuerySetBasedXMLRPCSerializer): diff --git a/tcms/xmlrpc/tests/test_serializer.py b/tcms/xmlrpc/tests/test_serializer.py index c250719334..bf10a40273 100644 --- a/tcms/xmlrpc/tests/test_serializer.py +++ b/tcms/xmlrpc/tests/test_serializer.py @@ -17,7 +17,6 @@ from tcms.tests.factories import TestCaseFactory from tcms.tests.factories import TestPlanFactory from tcms.tests.factories import UserFactory -from tcms.tests.factories import TestAttachmentFactory class TestXMLSerializer(test.TestCase): @@ -79,7 +78,7 @@ class MockTestPlanSerializer(QuerySetBasedXMLRPCSerializer): 'alias': {'product_version': 'default_product_version'}, } - m2m_fields = ('attachment', 'case') + m2m_fields = ('case',) class MockTestCaseSerializer(QuerySetBasedXMLRPCSerializer): @@ -113,11 +112,9 @@ def setUpTestData(cls): cls.case_author = UserFactory() cls.plans = [ - TestPlanFactory(attachment=[TestAttachmentFactory(), TestAttachmentFactory()]), - TestPlanFactory(attachment=[TestAttachmentFactory()]), - TestPlanFactory(attachment=[TestAttachmentFactory(), - TestAttachmentFactory(), - TestAttachmentFactory()]), + TestPlanFactory(), + TestPlanFactory(), + TestPlanFactory(), ] TestCaseFactory(author=cls.case_author, default_tester=None, plan=[cls.plans[0]]) TestCaseFactory(author=cls.case_author, default_tester=None, plan=[cls.plans[0]])