:p
atchew
Login
[Paolo: I think it might be more interesting to use the mailing list for reviewing future Patchew patches, for the sake of dogfooding and for Patchew's essential mission, also due to the inconvenience with the github web interface. So I created https://patchew.org/Patchew] v2: Add "parent_project" link in REST API. "Patches applied succeeded". Add colorbox link to git log. Fam Zheng (2): git: Improve info if git URL/repo/tag field missing subproject support api/migrations/0022_project_parent_project.py | 21 ++++++++++++++ api/models.py | 17 +++++++++-- api/rest.py | 2 +- api/search.py | 2 +- mods/git.py | 39 ++++++++++++++++++-------- tests/data/0019-libvirt-python.mbox.gz | Bin 0 -> 1816 bytes tests/data/0020-libvirt.mbox.gz | Bin 0 -> 4034 bytes tests/test_import.py | 26 +++++++++++++++++ www/templates/project-detail.html | 9 ++++++ www/views.py | 4 +-- 10 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 api/migrations/0022_project_parent_project.py create mode 100644 tests/data/0019-libvirt-python.mbox.gz create mode 100644 tests/data/0020-libvirt.mbox.gz -- 2.14.3
Signed-off-by: Fam Zheng <famz@redhat.com> --- mods/git.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/mods/git.py b/mods/git.py index XXXXXXX..XXXXXXX 100644 --- a/mods/git.py +++ b/mods/git.py @@ -XXX,XX +XXX,XX @@ class GitModule(PatchewModule): git_url = message.get_property("git.url") git_repo = message.get_property("git.repo") git_tag = message.get_property("git.tag") - message.status_tags.append({ - "url": git_url, - "title": format_html("Applied as tag {} in repo {}", git_tag, git_repo), - "type": "info", - "char": "G", + if git_url: + message.status_tags.append({ + "url": git_url, + "title": format_html("Applied as tag {} in repo {}", git_tag, git_repo), + "type": "info", + "char": "G", + }) + if git_repo and git_tag: + message.extra_status.append({ + "kind": "good", + "html": format_html('Patches applied successfully (<a href="{}">tree</a>, {}).<br/><samp>git fetch {} {}</samp>', + git_url, colorbox_a, git_repo, git_tag), + "extra": colorbox_div, + "id": "gitlog" + }) + else: + message.status_tags.append({ + "title": format_html("Patches applied succeeded"), + "type": "info", + "char": "G", + }) + message.extra_status.append({ + "kind": "good", + "html": format_html('Patches applied successfully ({})', + colorbox_a), + "extra": colorbox_div, + "id": "gitlog" }) - message.extra_status.append({ - "kind": "good", - "html": format_html('Patches applied successfully (<a href="{}">tree</a>, {}).<br/><samp>git fetch {} {}</samp>', - git_url, colorbox_a, git_repo, git_tag), - "extra": colorbox_div, - "id": "gitlog" - }) if request.user.is_authenticated: if message.get_property("git.apply-failed") != None or \ message.get_property("git.need-apply") == None: -- 2.14.3
This extends Project model to allow a simple (two-level) "subproject" hierarchy. A parent_project field is added. It can be NULL for the usual top-level projects, or can point to an existing top-level project, to make a "subproject". The project list page only shows top-level project. But the series list page shows series in both the top-level project or in a subprojct. The link to a subproject's information page is added in the top-level one's. Signed-off-by: Fam Zheng <famz@redhat.com> --- api/migrations/0022_project_parent_project.py | 21 +++++++++++++++++++++ api/models.py | 17 +++++++++++++++-- api/rest.py | 2 +- api/search.py | 2 +- tests/data/0019-libvirt-python.mbox.gz | Bin 0 -> 1816 bytes tests/data/0020-libvirt.mbox.gz | Bin 0 -> 4034 bytes tests/test_import.py | 26 ++++++++++++++++++++++++++ www/templates/project-detail.html | 9 +++++++++ www/views.py | 4 ++-- 9 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 api/migrations/0022_project_parent_project.py create mode 100644 tests/data/0019-libvirt-python.mbox.gz create mode 100644 tests/data/0020-libvirt.mbox.gz diff --git a/api/migrations/0022_project_parent_project.py b/api/migrations/0022_project_parent_project.py new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/api/migrations/0022_project_parent_project.py @@ -XXX,XX +XXX,XX @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.10 on 2018-02-14 06:19 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0021_fix_recipients_utf8'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='parent_project', + field=models.ForeignKey(blank=True, help_text='Parent project which this\n project belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='api.Project'), + ), + ] diff --git a/api/models.py b/api/models.py index XXXXXXX..XXXXXXX 100644 --- a/api/models.py +++ b/api/models.py @@ -XXX,XX +XXX,XX @@ class Project(models.Model): display_order = models.IntegerField(default=0, help_text="""Order number of the project to display, higher number first""") + parent_project = models.ForeignKey('Project', on_delete=models.CASCADE, + blank=True, null=True, + help_text="""Parent project which this + project belongs to. The parent must be a + top project which has + parent_project=NULL""") + def __str__(self): return self.name @@ -XXX,XX +XXX,XX @@ class Project(models.Model): return True return False + def get_subprojects(self): + return Project.objects.filter(parent_project=self) + class ProjectProperty(models.Model): project = models.ForeignKey('Project', on_delete=models.CASCADE) name = models.CharField(max_length=1024, db_index=True) @@ -XXX,XX +XXX,XX @@ class MessageManager(models.Manager): q = super(MessageManager, self).get_queryset()\ .filter(is_series_head=True).prefetch_related('properties', 'project') if isinstance(project, str): - q = q.filter(project__name=project) + po = Project.objects.get(name=project) elif isinstance(project, int): - q = q.filter(project__id=project) + po = Project.objects.get(id=project) + else: + return q + q = q.filter(project=po) | q.filter(project__parent_project=po) return q def find_series(self, message_id, project_name=None): diff --git a/api/rest.py b/api/rest.py index XXXXXXX..XXXXXXX 100644 --- a/api/rest.py +++ b/api/rest.py @@ -XXX,XX +XXX,XX @@ class ProjectSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Project fields = ('resource_uri', 'name', 'mailing_list', 'prefix_tags', 'url', 'git', \ - 'description', 'display_order', 'logo') + 'description', 'display_order', 'logo', 'parent_project') class ProjectsViewSet(viewsets.ModelViewSet): queryset = Project.objects.all().order_by('id') diff --git a/api/search.py b/api/search.py index XXXXXXX..XXXXXXX 100644 --- a/api/search.py +++ b/api/search.py @@ -XXX,XX +XXX,XX @@ Search text keyword in the email message. Example: elif term.startswith("project:"): cond = term[term.find(":") + 1:] self._projects.add(cond) - q = Q(project__name=cond) + q = Q(project__name=cond) | Q(project__parent_project__name=cond) else: # Keyword in subject is the default q = as_keywords(term) diff --git a/tests/data/0019-libvirt-python.mbox.gz b/tests/data/0019-libvirt-python.mbox.gz new file mode 100644 index XXXXXXX..XXXXXXX GIT binary patch literal 1816 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 G5dZ*bB%nP2 literal 0 HcmV?d00001 diff --git a/tests/data/0020-libvirt.mbox.gz b/tests/data/0020-libvirt.mbox.gz new file mode 100644 index XXXXXXX..XXXXXXX GIT binary patch literal 4034 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 literal 0 HcmV?d00001 diff --git a/tests/test_import.py b/tests/test_import.py index XXXXXXX..XXXXXXX 100755 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -XXX,XX +XXX,XX @@ import os sys.path.append(os.path.dirname(__file__)) from patchewtest import PatchewTestCase, main import json +from api.models import Message class ImportTest(PatchewTestCase): @@ -XXX,XX +XXX,XX @@ class ImportTest(PatchewTestCase): self.check_cli(["search"], stdout='[edk2] [PATCH 0/3] Revert "ShellPkg: Fix echo to support displaying special characters"') + def test_import_to_subproject(self): + tp = self.add_project("Libvirt", "libvir-list@redhat.com, libvirt-list@redhat.com", + "git://libvirt.org/libvirt.git") + tp.prefix_tags = "!python" + tp.save() + sp = self.add_project("Libvirt-python", "libvir-list@redhat.com, libvirt-list@redhat.com", + "https://github.com/libvirt/libvirt-python") + sp.prefix_tags = "python" + sp.parent_project = tp + sp.save() + self.cli_import("0019-libvirt-python.mbox.gz") + subj = '[libvirt] [python PATCH] event-test.py: Remove extra ( in --help output' + self.check_cli(["search", "project:Libvirt"], stdout=subj) + self.check_cli(["search", "project:Libvirt-python"], stdout=subj) + sh = Message.objects.series_heads() + self.assertEqual(len(sh), 1) + s = sh[0] + self.assertTrue(s.get_property("git.need-apply", True)) + self.assertTrue(s.project.name, sp.name) + + self.cli_import("0020-libvirt.mbox.gz") + subj2 = subj + '\n[libvirt] [PATCH v2] vcpupin: add clear feature' + self.check_cli(["search", "project:Libvirt"], stdout=subj2) + self.check_cli(["search", "project:Libvirt-python"], stdout=subj) + class UnprivilegedImportTest(ImportTest): def setUp(self): self.create_superuser() diff --git a/www/templates/project-detail.html b/www/templates/project-detail.html index XXXXXXX..XXXXXXX 100644 --- a/www/templates/project-detail.html +++ b/www/templates/project-detail.html @@ -XXX,XX +XXX,XX @@ <div class="status-content"> <span class="fa fa-lg fa-git"></span><div>Git: <a href="{{ project.git }}">{{ project.git }}</a></div> </div> +{% if project.get_subprojects %} +<div class="status-content"> + <span class="fa fa-lg fa-sitemap"></span><div>Subprojects: + {% for p in project.get_subprojects %} + <a href="{% url "project_detail" project=p %}">{{ p.name }}</a> + {% endfor %} + </div> +</div> +{% endif %} {% for status in project.extra_status %} <div class="status-content{% if status.kind %} status-{{ status.kind }}{% endif %}"> {% if status.icon %}<span class="fa fa-lg fa-{{ status.icon }}"></span> diff --git a/www/views.py b/www/views.py index XXXXXXX..XXXXXXX 100644 --- a/www/views.py +++ b/www/views.py @@ -XXX,XX +XXX,XX @@ def prepare_series_list(request, sl): return [prepare_message(request, s, False) for s in sl] def prepare_projects(): - return api.models.Project.objects.all().order_by('-display_order', 'name') + return api.models.Project.objects.filter(parent_project=None).order_by('-display_order', 'name') def view_project_list(request): - return render_page(request, "project-list.html", projects=prepare_projects) + return render_page(request, "project-list.html", projects=prepare_projects()) def gen_page_links(total, cur_page, pagesize, extra_params): max_page = int((total + pagesize - 1) / pagesize) -- 2.14.3
[Paolo: I think it might be more interesting to use the mailing list for reviewing future Patchew patches, for the sake of dogfooding and for Patchew's essential mission, also due to the inconvenience with the github web interface. So I created https://patchew.org/Patchew] v4: Add tests. v3: Rebase onto master. v2: Add "parent_project" link in REST API. "Patches applied succeeded". Add colorbox link to git log. Fam Zheng (3): git: Improve info if git URL/repo/tag field missing subproject support tests: Add basic tests for subprojects api/migrations/0022_project_parent_project.py | 21 ++++++++++++++++ api/models.py | 17 +++++++++++-- api/rest.py | 2 +- api/search.py | 2 +- mods/git.py | 35 ++++++++++++++++++-------- tests/data/0019-libvirt-python.mbox.gz | Bin 0 -> 1816 bytes tests/data/0020-libvirt.mbox.gz | Bin 0 -> 4034 bytes tests/test_import.py | 26 +++++++++++++++++++ tests/test_rest.py | 19 +++++++++++++- www/templates/project-detail.html | 9 +++++++ www/views.py | 4 +-- 11 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 api/migrations/0022_project_parent_project.py create mode 100644 tests/data/0019-libvirt-python.mbox.gz create mode 100644 tests/data/0020-libvirt.mbox.gz -- 2.14.3
Signed-off-by: Fam Zheng <famz@redhat.com> --- mods/git.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/mods/git.py b/mods/git.py index XXXXXXX..XXXXXXX 100644 --- a/mods/git.py +++ b/mods/git.py @@ -XXX,XX +XXX,XX @@ class GitModule(PatchewModule): git_url = message.get_property("git.url") git_repo = message.get_property("git.repo") git_tag = message.get_property("git.tag") - message.status_tags.append({ - "url": git_url, - "title": format_html("Applied as tag {} in repo {}", git_tag, git_repo), - "type": "info", - "char": "G", + if git_url: + message.status_tags.append({ + "url": git_url, + "title": format_html("Applied as tag {} in repo {}", git_tag, git_repo), + "type": "info", + "char": "G", + }) + if git_repo and git_tag: + message.extra_status.append({ + "kind": "good", + "html": format_html('Patches applied successfully (<a href="{}">tree</a>, {}).<br/><samp>git fetch {} {}</samp>', + git_url, colorbox_a, git_repo, git_tag), + }) + else: + message.status_tags.append({ + "title": format_html("Patches applied succeeded"), + "type": "info", + "char": "G", + }) + message.extra_status.append({ + "kind": "good", + "html": format_html('Patches applied successfully ({})', + colorbox_a), + "extra": colorbox_div, + "id": "gitlog" }) - message.extra_status.append({ - "kind": "good", - "html": format_html('Patches applied successfully (<a href="{}">tree</a>, {}).<br/><samp>git fetch {} {}</samp>', - git_url, colorbox_a, git_repo, git_tag), - }) if request.user.is_authenticated: if message.get_property("git.apply-failed") != None or \ message.get_property("git.need-apply") == None: -- 2.14.3
This extends Project model to allow a simple (two-level) "subproject" hierarchy. A parent_project field is added. It can be NULL for the usual top-level projects, or can point to an existing top-level project, to make a "subproject". The project list page only shows top-level project. But the series list page shows series in both the top-level project or in a subprojct. The link to a subproject's information page is added in the top-level one's. Signed-off-by: Fam Zheng <famz@redhat.com> --- api/migrations/0022_project_parent_project.py | 21 +++++++++++++++++++++ api/models.py | 17 +++++++++++++++-- api/rest.py | 2 +- api/search.py | 2 +- tests/data/0019-libvirt-python.mbox.gz | Bin 0 -> 1816 bytes tests/data/0020-libvirt.mbox.gz | Bin 0 -> 4034 bytes tests/test_import.py | 26 ++++++++++++++++++++++++++ www/templates/project-detail.html | 9 +++++++++ www/views.py | 4 ++-- 9 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 api/migrations/0022_project_parent_project.py create mode 100644 tests/data/0019-libvirt-python.mbox.gz create mode 100644 tests/data/0020-libvirt.mbox.gz diff --git a/api/migrations/0022_project_parent_project.py b/api/migrations/0022_project_parent_project.py new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/api/migrations/0022_project_parent_project.py @@ -XXX,XX +XXX,XX @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.10 on 2018-02-14 06:19 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0021_fix_recipients_utf8'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='parent_project', + field=models.ForeignKey(blank=True, help_text='Parent project which this\n project belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='api.Project'), + ), + ] diff --git a/api/models.py b/api/models.py index XXXXXXX..XXXXXXX 100644 --- a/api/models.py +++ b/api/models.py @@ -XXX,XX +XXX,XX @@ class Project(models.Model): display_order = models.IntegerField(default=0, help_text="""Order number of the project to display, higher number first""") + parent_project = models.ForeignKey('Project', on_delete=models.CASCADE, + blank=True, null=True, + help_text="""Parent project which this + project belongs to. The parent must be a + top project which has + parent_project=NULL""") + def __str__(self): return self.name @@ -XXX,XX +XXX,XX @@ class Project(models.Model): return True return False + def get_subprojects(self): + return Project.objects.filter(parent_project=self) + class ProjectProperty(models.Model): project = models.ForeignKey('Project', on_delete=models.CASCADE) name = models.CharField(max_length=1024, db_index=True) @@ -XXX,XX +XXX,XX @@ class MessageManager(models.Manager): q = super(MessageManager, self).get_queryset()\ .filter(is_series_head=True).prefetch_related('properties', 'project') if isinstance(project, str): - q = q.filter(project__name=project) + po = Project.objects.get(name=project) elif isinstance(project, int): - q = q.filter(project__id=project) + po = Project.objects.get(id=project) + else: + return q + q = q.filter(project=po) | q.filter(project__parent_project=po) return q def find_series(self, message_id, project_name=None): diff --git a/api/rest.py b/api/rest.py index XXXXXXX..XXXXXXX 100644 --- a/api/rest.py +++ b/api/rest.py @@ -XXX,XX +XXX,XX @@ class ProjectSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Project fields = ('resource_uri', 'name', 'mailing_list', 'prefix_tags', 'url', 'git', \ - 'description', 'display_order', 'logo') + 'description', 'display_order', 'logo', 'parent_project') class ProjectsViewSet(viewsets.ModelViewSet): queryset = Project.objects.all().order_by('id') diff --git a/api/search.py b/api/search.py index XXXXXXX..XXXXXXX 100644 --- a/api/search.py +++ b/api/search.py @@ -XXX,XX +XXX,XX @@ Search text keyword in the email message. Example: elif term.startswith("project:"): cond = term[term.find(":") + 1:] self._projects.add(cond) - q = Q(project__name=cond) + q = Q(project__name=cond) | Q(project__parent_project__name=cond) else: # Keyword in subject is the default q = as_keywords(term) diff --git a/tests/data/0019-libvirt-python.mbox.gz b/tests/data/0019-libvirt-python.mbox.gz new file mode 100644 index XXXXXXX..XXXXXXX GIT binary patch literal 1816 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 G5dZ*bB%nP2 literal 0 HcmV?d00001 diff --git a/tests/data/0020-libvirt.mbox.gz b/tests/data/0020-libvirt.mbox.gz new file mode 100644 index XXXXXXX..XXXXXXX GIT binary patch literal 4034 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 literal 0 HcmV?d00001 diff --git a/tests/test_import.py b/tests/test_import.py index XXXXXXX..XXXXXXX 100755 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -XXX,XX +XXX,XX @@ import os sys.path.append(os.path.dirname(__file__)) from patchewtest import PatchewTestCase, main import json +from api.models import Message class ImportTest(PatchewTestCase): @@ -XXX,XX +XXX,XX @@ class ImportTest(PatchewTestCase): self.check_cli(["search"], stdout='[edk2] [PATCH 0/3] Revert "ShellPkg: Fix echo to support displaying special characters"') + def test_import_to_subproject(self): + tp = self.add_project("Libvirt", "libvir-list@redhat.com, libvirt-list@redhat.com", + "git://libvirt.org/libvirt.git") + tp.prefix_tags = "!python" + tp.save() + sp = self.add_project("Libvirt-python", "libvir-list@redhat.com, libvirt-list@redhat.com", + "https://github.com/libvirt/libvirt-python") + sp.prefix_tags = "python" + sp.parent_project = tp + sp.save() + self.cli_import("0019-libvirt-python.mbox.gz") + subj = '[libvirt] [python PATCH] event-test.py: Remove extra ( in --help output' + self.check_cli(["search", "project:Libvirt"], stdout=subj) + self.check_cli(["search", "project:Libvirt-python"], stdout=subj) + sh = Message.objects.series_heads() + self.assertEqual(len(sh), 1) + s = sh[0] + self.assertTrue(s.get_property("git.need-apply", True)) + self.assertTrue(s.project.name, sp.name) + + self.cli_import("0020-libvirt.mbox.gz") + subj2 = subj + '\n[libvirt] [PATCH v2] vcpupin: add clear feature' + self.check_cli(["search", "project:Libvirt"], stdout=subj2) + self.check_cli(["search", "project:Libvirt-python"], stdout=subj) + class UnprivilegedImportTest(ImportTest): def setUp(self): self.create_superuser() diff --git a/www/templates/project-detail.html b/www/templates/project-detail.html index XXXXXXX..XXXXXXX 100644 --- a/www/templates/project-detail.html +++ b/www/templates/project-detail.html @@ -XXX,XX +XXX,XX @@ <div class="status-content"> <span class="fa fa-lg fa-git"></span><div>Git: <a href="{{ project.git }}">{{ project.git }}</a></div> </div> +{% if project.get_subprojects %} +<div class="status-content"> + <span class="fa fa-lg fa-sitemap"></span><div>Subprojects: + {% for p in project.get_subprojects %} + <a href="{% url "project_detail" project=p %}">{{ p.name }}</a> + {% endfor %} + </div> +</div> +{% endif %} {% for status in project.extra_status %} <div class="status-content{% if status.kind %} status-{{ status.kind }}{% endif %}"> {% if status.icon %}<span class="fa fa-lg fa-{{ status.icon }}"></span> diff --git a/www/views.py b/www/views.py index XXXXXXX..XXXXXXX 100644 --- a/www/views.py +++ b/www/views.py @@ -XXX,XX +XXX,XX @@ def prepare_series_list(request, sl): return [prepare_message(request, s, False) for s in sl] def prepare_projects(): - return api.models.Project.objects.all().order_by('-display_order', 'name') + return api.models.Project.objects.filter(parent_project=None).order_by('-display_order', 'name') def view_project_list(request): - return render_page(request, "project-list.html", projects=prepare_projects) + return render_page(request, "project-list.html", projects=prepare_projects()) def gen_page_links(total, cur_page, pagesize, extra_params): max_page = int((total + pagesize - 1) / pagesize) -- 2.14.3
Signed-off-by: Fam Zheng <famz@redhat.com> --- tests/test_rest.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/test_rest.py b/tests/test_rest.py index XXXXXXX..XXXXXXX 100755 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -XXX,XX +XXX,XX @@ class RestTest(PatchewTestCase): self.p = self.add_project("QEMU", "qemu-devel@nongnu.org") self.PROJECT_BASE = '%sprojects/%d/' % (self.REST_BASE, self.p.id) + self.sp = self.add_project("QEMU Block Layer", "qemu-block@nongnu.org") + self.sp.parent_project = self.p + self.sp.save() + self.SUBPROJECT_BASE = '%sprojects/%d/' % (self.REST_BASE, self.sp.id) + self.admin = User.objects.get(username='admin') self.USER_BASE = '%susers/%d/' % (self.REST_BASE, self.admin.id) @@ -XXX,XX +XXX,XX @@ class RestTest(PatchewTestCase): def test_projects(self): resp = self.api_client.get(self.REST_BASE + 'projects/') - self.assertEquals(resp.data['count'], 1) + self.assertEquals(resp.data['count'], 2) self.assertEquals(resp.data['results'][0]['resource_uri'], self.PROJECT_BASE) self.assertEquals(resp.data['results'][0]['name'], "QEMU") self.assertEquals(resp.data['results'][0]['mailing_list'], "qemu-devel@nongnu.org") + self.assertEquals(resp.data['results'][1]['resource_uri'], self.SUBPROJECT_BASE) + self.assertEquals(resp.data['results'][1]['name'], "QEMU Block Layer") + self.assertEquals(resp.data['results'][1]['mailing_list'], "qemu-block@nongnu.org") + self.assertEquals(resp.data['results'][1]['parent_project'], self.PROJECT_BASE) def test_project(self): resp = self.api_client.get(self.PROJECT_BASE) self.assertEquals(resp.data['resource_uri'], self.PROJECT_BASE) self.assertEquals(resp.data['name'], "QEMU") self.assertEquals(resp.data['mailing_list'], "qemu-devel@nongnu.org") + resp = self.api_client.get(self.SUBPROJECT_BASE) + self.assertEquals(resp.data['resource_uri'], self.SUBPROJECT_BASE) + self.assertEquals(resp.data['name'], "QEMU Block Layer") + self.assertEquals(resp.data['mailing_list'], "qemu-block@nongnu.org") + self.assertEquals(resp.data['parent_project'], self.PROJECT_BASE) def test_project_post_minimal(self): data = { @@ -XXX,XX +XXX,XX @@ class RestTest(PatchewTestCase): 'git': 'https://gitlab.com/keycodemap/keycodemapdb/', 'description': 'keycodemapdb generates code to translate key codes', 'display_order': 4321, + 'parent_project': self.PROJECT_BASE, } resp = self.api_client.post(self.REST_BASE + 'projects/', data=data) self.assertEquals(resp.status_code, 201) @@ -XXX,XX +XXX,XX @@ class RestTest(PatchewTestCase): self.assertEquals(resp.data['description'], data['description']) self.assertEquals(resp.data['display_order'], data['display_order']) self.assertEquals(resp.data['logo'], None) + self.assertEquals(resp.data['parent_project'], self.PROJECT_BASE) resp = self.api_client.get(resp.data['resource_uri']) self.assertEquals(resp.data['name'], data['name']) @@ -XXX,XX +XXX,XX @@ class RestTest(PatchewTestCase): self.assertEquals(resp.data['description'], data['description']) self.assertEquals(resp.data['display_order'], data['display_order']) self.assertEquals(resp.data['logo'], None) + self.assertEquals(resp.data['parent_project'], self.PROJECT_BASE) def test_series_single(self): resp = self.apply_and_retrieve('0001-simple-patch.mbox.gz', -- 2.14.3