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 }}
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 }}
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 }})
- 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]])