Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopts new content_summary layout #2

Merged
merged 1 commit into from
Mar 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ else
git fetch origin +refs/pull/$PULP_PLUGIN_PR_NUMBER/merge
git checkout FETCH_HEAD
pip install -e .
cd ../pulp
cd ../pulpcore
fi

if [ -z "$PULP_FILE_PR_NUMBER" ]; then
Expand All @@ -37,7 +37,7 @@ else
git fetch origin +refs/pull/$PULP_FILE_PR_NUMBER/merge
git checkout FETCH_HEAD
pip install -e .
cd ../pulp
cd ../pulpcore
fi

if [ ! -z "$PULP_SMASH_PR_NUMBER" ]; then
Expand All @@ -48,5 +48,5 @@ if [ ! -z "$PULP_SMASH_PR_NUMBER" ]; then
git fetch origin +refs/pull/$PULP_SMASH_PR_NUMBER/merge
git checkout FETCH_HEAD
pip install -e .
cd ../pulp
cd ../pulpcore
fi
24 changes: 24 additions & 0 deletions pulpcore/app/migrations/0003_repositoryversioncontentdetails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 2.1.7 on 2019-02-25 22:56

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('pulp_app', '0002_task_name'),
]

operations = [
migrations.CreateModel(
name='RepositoryVersionContentDetails',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('count_type', models.CharField(choices=[('A', 'added'), ('P', 'present'), ('R', 'removed')], max_length=1)),
('content_type', models.TextField()),
('count', models.IntegerField()),
('repository_version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='counts', to='pulp_app.RepositoryVersion')),
],
),
]
1 change: 1 addition & 0 deletions pulpcore/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Repository,
RepositoryContent,
RepositoryVersion,
RepositoryVersionContentDetails,
)

from .task import CreatedResource, ReservedResource, Task, TaskReservedResource, Worker # noqa
Expand Down
90 changes: 89 additions & 1 deletion pulpcore/app/models/repository.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
"""
Repository related Django models.
"""
import django

from contextlib import suppress
from django.db import models
from django.db import transaction
from django.urls import reverse

from .base import Model, MasterModel
from .content import Content
from .task import CreatedResource

from pulpcore.app.models.storage import get_tls_path
from pulpcore.app.util import get_view_name_for_model
from pulpcore.exceptions import ResourceImmutableError


Expand Down Expand Up @@ -285,6 +289,7 @@ def contains(self, content):
def create(cls, repository, base_version=None):
"""
Create a new RepositoryVersion

Creation of a RepositoryVersion should be done in a RQ Job.

Args:
Expand All @@ -295,7 +300,6 @@ def create(cls, repository, base_version=None):
Returns:
pulpcore.app.models.RepositoryVersion: The Created RepositoryVersion
"""

with transaction.atomic():
version = cls(
repository=repository,
Expand Down Expand Up @@ -456,6 +460,35 @@ def delete(self, **kwargs):
self.repository.save()
super().delete(**kwargs)

def compute_counts(self):
"""
Compute and save content unit counts by type.

Count records are stored as :class:`~pulpcore.app.models.RepositoryVersionContentDetails`.
This method deletes existing :class:`~pulpcore.app.models.RepositoryVersionContentDetails`
objects and makes new ones with each call.
"""
with transaction.atomic():
counts_list = []
for value, name in RepositoryVersionContentDetails.COUNT_TYPE_CHOICES:
RepositoryVersionContentDetails.objects.filter(repository_version=self).delete()
if value == RepositoryVersionContentDetails.ADDED:
qs = self.added()
elif value == RepositoryVersionContentDetails.PRESENT:
qs = self.content
elif value == RepositoryVersionContentDetails.REMOVED:
qs = self.removed()
annotated = qs.values('_type').annotate(count=models.Count('_type'))
for item in annotated:
count_obj = RepositoryVersionContentDetails(
content_type=item['_type'],
repository_version=self,
count=item['count'],
count_type=value,
)
counts_list.append(count_obj)
RepositoryVersionContentDetails.objects.bulk_create(counts_list)

def __enter__(self):
"""
Create the repository version
Expand All @@ -474,3 +507,58 @@ def __exit__(self, exc_type, exc_value, traceback):
else:
self.complete = True
self.save()
self.compute_counts()


class RepositoryVersionContentDetails(models.Model):
ADDED = 'A'
PRESENT = 'P'
REMOVED = 'R'
COUNT_TYPE_CHOICES = (
(ADDED, 'added'),
(PRESENT, 'present'),
(REMOVED, 'removed'),
)

count_type = models.CharField(
max_length=1,
choices=COUNT_TYPE_CHOICES,
)
content_type = models.TextField()
repository_version = models.ForeignKey('RepositoryVersion', related_name='counts',
on_delete=models.CASCADE)
count = models.IntegerField()

@property
def content_href(self):
"""
Generate URLs for the content types present in the RepositoryVersion.

For each content type present in the RepositoryVersion, create the URL of the viewset of
that variety of content along with a query parameter which filters it by presence in this
RepositoryVersion.

Args:
obj (pulpcore.app.models.RepositoryVersion): The RepositoryVersion being serialized.
Returns:
dict: {<_type>: <url>}
"""
ctype_model = Content.objects.filter(_type=self.content_type).first().cast().__class__
ctype_view = get_view_name_for_model(ctype_model, 'list')
try:
ctype_url = reverse(ctype_view)
except django.urls.exceptions.NoReverseMatch:
# We've hit a content type for which there is no viewset.
# There's nothing we can do here, except to skip it.
return
rv_href = "/pulp/api/v3/repositories/{repo}/versions/{version}/".format(
repo=self.repository_version.repository.pk, version=self.repository_version.number)
if self.count_type == self.ADDED:
partial_url_str = "{base}?repository_version_added={rv_href}"
elif self.count_type == self.PRESENT:
partial_url_str = "{base}?repository_version={rv_href}"
elif self.count_type == self.REMOVED:
partial_url_str = "{base}?repository_version_removed={rv_href}"
full_url = partial_url_str.format(
base=ctype_url, rv_href=rv_href)
return full_url
167 changes: 13 additions & 154 deletions pulpcore/app/serializers/repository.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
from gettext import gettext as _

import django
from django.db.models import Count
from django.urls import reverse

from rest_framework import serializers, fields
from rest_framework.validators import UniqueValidator
from rest_framework_nested.serializers import NestedHyperlinkedModelSerializer
Expand All @@ -19,7 +15,6 @@
ModelSerializer,
)
from pulpcore.app.serializers import validate_unknown_fields
from pulpcore.app.util import get_view_name_for_model


class RepositorySerializer(ModelSerializer):
Expand Down Expand Up @@ -261,31 +256,9 @@ class RepositoryVersionSerializer(ModelSerializer, NestedHyperlinkedModelSeriali
lookup_field='number',
parent_lookup_kwargs={'repository_pk': 'repository__pk'},
)
content_hrefs = serializers.SerializerMethodField(
help_text=_('A mapping of the types of content in this version, and the HREF to view '
'them.'),
read_only=True,
)
content_added_hrefs = serializers.SerializerMethodField(
help_text=_('A mapping of the types of content added in this version, and the HREF to '
'view them.'),
read_only=True,
)
content_removed_hrefs = serializers.SerializerMethodField(
help_text=_('A mapping of the types of content removed from this version, and the HREF '
'to view them.'),
read_only=True,
)
content_summary = serializers.SerializerMethodField(
help_text=_('A list of counts of each type of content in this version.'),
read_only=True,
)
content_added_summary = serializers.SerializerMethodField(
help_text=_('A list of counts of each type of content added in this version.'),
read_only=True,
)
content_removed_summary = serializers.SerializerMethodField(
help_text=_('A list of counts of each type of content removed in this version.'),
help_text=_('Various count summaries of the content in the version and the HREF to view '
'them.'),
read_only=True,
)

Expand All @@ -294,136 +267,22 @@ def get_content_summary(self, obj):
The summary of contained content.

Returns:
dict: of {<_type>: <count>}
"""
annotated = obj.content.values('_type').annotate(count=Count('_type'))
return {c['_type']: c['count'] for c in annotated}

def get_content_added_summary(self, obj):
"""
The summary of added content.

Returns:
dict: of {<_type>: <count>}
"""
annotated = obj.added().values('_type').annotate(count=Count('_type'))
return {c['_type']: c['count'] for c in annotated}

def get_content_removed_summary(self, obj):
"""
The summary of removed content.

Returns:
dict: of {<_type>: <count>}
"""
annotated = obj.removed().values('_type').annotate(count=Count('_type'))
return {c['_type']: c['count'] for c in annotated}

def get_content_hrefs(self, obj):
"""
Generate URLs for the content types present in the RepositoryVersion.

For each content type present in the RepositoryVersion, create the URL of the viewset of
that variety of content along with a query parameter which filters it by presence in this
RepositoryVersion.

Args:
obj (pulpcore.app.models.RepositoryVersion): The RepositoryVersion being serialized.

Returns:
dict: {<_type>: <url>}
dict: of {'added': {<_type>: {'count': <count>, 'href': <href>},
'removed': {<_type>: {'count': <count>, 'href': <href>},
'present': {<_type>: {'count': <count>, 'href': <href>},
}
"""
content_urls = {}

for ctype in obj.content.values_list('_type', flat=True).distinct():
ctype_model = obj.content.filter(_type=ctype).first().cast().__class__
ctype_view = get_view_name_for_model(ctype_model, 'list')
try:
ctype_url = reverse(ctype_view)
except django.urls.exceptions.NoReverseMatch:
# We've hit a content type for which there is no viewset.
# There's nothing we can do here, except to skip it.
continue
rv_href = "/pulp/api/v3/repositories/{repo}/versions/{version}/".format(
repo=obj.repository.pk, version=obj.number)
full_url = "{base}?repository_version={rv_href}".format(
base=ctype_url, rv_href=rv_href)
content_urls[ctype] = full_url

return content_urls

def get_content_added_hrefs(self, obj):
"""
Generate URLs for the content types added in the RepositoryVersion.

For each content type added in the RepositoryVersion, create the URL of the viewset of
that variety of content along with a query parameter which filters it by addition in this
RepositoryVersion.

Args:
obj (pulpcore.app.models.RepositoryVersion): The RepositoryVersion being serialized.

Returns:
dict: {<_type>: <url>}
"""
content_urls = {}

for ctype in obj.added().values_list('_type', flat=True).distinct():
ctype_model = obj.content.filter(_type=ctype).first().cast().__class__
ctype_view = get_view_name_for_model(ctype_model, 'list')
try:
ctype_url = reverse(ctype_view)
except django.urls.exceptions.NoReverseMatch:
# We've hit a content type for which there is no viewset.
# There's nothing we can do here, except to skip it.
continue
rv_href = "/pulp/api/v3/repositories/{repo}/versions/{version}/".format(
repo=obj.repository.pk, version=obj.number)
full_url = "{base}?repository_version_added={rv_href}".format(
base=ctype_url, rv_href=rv_href)
content_urls[ctype] = full_url

return content_urls

def get_content_removed_hrefs(self, obj):
"""
Generate URLs for the content types removed in the RepositoryVersion.

For each content type removed in the RepositoryVersion, create the URL of the viewset of
that variety of content along with a query parameter which filters it by removal in this
RepositoryVersion.

Args:
obj (pulpcore.app.models.RepositoryVersion): The RepositoryVersion being serialized.

Returns:
dict: {<_type>: <url>}
"""
content_urls = {}

for ctype in obj.removed().values_list('_type', flat=True).distinct():
ctype_model = obj.content.filter(_type=ctype).first().cast().__class__
ctype_view = get_view_name_for_model(ctype_model, 'list')
try:
ctype_url = reverse(ctype_view)
except django.urls.exceptions.NoReverseMatch:
# We've hit a content type for which there is no viewset.
# There's nothing we can do here, except to skip it.
continue
rv_href = "/pulp/api/v3/repositories/{repo}/versions/{version}/".format(
repo=obj.repository.pk, version=obj.number)
full_url = "{base}?repository_version_removed={rv_href}".format(
base=ctype_url, rv_href=rv_href)
content_urls[ctype] = full_url

return content_urls
to_return = {'added': {}, 'removed': {}, 'present': {}}
for count_detail in obj.counts.all():
count_type = count_detail.get_count_type_display()
item_dict = {'count': count_detail.count, 'href': count_detail.content_href}
to_return[count_type][count_detail.content_type] = item_dict
return to_return

class Meta:
model = models.RepositoryVersion
fields = ModelSerializer.Meta.fields + (
'_href', 'number', 'base_version',
'content_hrefs', 'content_added_hrefs', 'content_removed_hrefs',
'content_summary', 'content_added_summary', 'content_removed_summary',
'_href', 'number', 'base_version', 'content_summary',
)


Expand Down