Merge pull request #57 from nextcloud/categories

Move categories into separate route because otherwise it duplicates a…
This commit is contained in:
Lukas Reschke 2016-06-26 12:37:34 +02:00 коммит произвёл GitHub
Родитель 0581c4ae21 5083dfb807
Коммит 2800786415
4 изменённых файлов: 178 добавлений и 65 удалений

Просмотреть файл

@ -12,6 +12,8 @@ The following API routes are present:
* :ref:`api-all-releases`
* :ref:`api-all-categories`
* :ref:`api-delete-app`
* :ref:`api-delete-release`
@ -45,85 +47,91 @@ This route will return all releases to display inside Nextcloud's apps admin are
{
"id": "news",
"categories": [
{
"id": "tools",
"translations": {
"en": {
"name": "Tools"
},
"de": {
"name": "Werkzeuge"
},
"fr": {
"name": "Outil"
}
}
}
"multimedia"
],
"recommendations": 100,
"featured": false,
"userDocs": "http://127.0.0.1:8000/user",
"adminDocs": "http://127.0.0.1:8000/admin",
"developerDocs": "http://127.0.0.1:8000/dev",
"issueTracker": "http://127.0.0.1:8000/issue",
"website": "http://127.0.0.1:8000/",
"created": "2016-06-09T17:56:05.076980Z",
"lastModified": "2016-06-09T17:56:19.099038Z",
"userDocs": "https://github.com/owncloud/news/wiki#user-documentation",
"adminDocs": "https://github.com/owncloud/news#readme",
"developerDocs": "https://github.com/owncloud/news/wiki#developer-documentation",
"issueTracker": "https://github.com/owncloud/news/issues",
"website": "https://github.com/owncloud/news",
"created": "2016-06-25T16:08:56.794719Z",
"lastModified": "2016-06-25T16:49:25.326855Z",
"releases": [
{
"version": "1.9.0",
"checksum": "65e613318107bceb131af5cf8b71e773b79e1a9476506f502c8e2017b52aba15",
"version": "8.8.0",
"phpExtensions": [
{
"id": "SimpleXML",
"versionSpec": "*"
},
{
"id": "curl",
"versionSpec": "*"
},
{
"id": "iconv",
"versionSpec": "*"
},
{
"id": "libxml",
"versionSpec": ">=3.0.0 <5.0.0"
"versionSpec": ">=2.7.8"
}
],
"databases": [
{
"id": "mysql",
"versionSpec": ">=5.5.0"
},
{
"id": "pgsql",
"versionSpec": ">=9.4.0"
},
{
"id": "sqlite",
"name": "Sqlite",
"versionSpec": "*"
}
],
"shellCommands": [
"grep"
],
"phpVersionSpec": "<7.0.0",
"platformVersionSpec": ">=9.0.0",
"phpVersionSpec": ">=5.6.0",
"platformVersionSpec": ">=9.0.0 <9.2.0",
"minIntSize": 64,
"download": "https://127.0.0.1:8000/download",
"created": "2016-06-09T17:57:00.587076Z",
"lastModified": "2016-06-09T17:57:00.587238Z"
"download": "https://github.com/owncloud/news/releases/download/8.8.0/news.tar.gz",
"created": "2016-06-25T16:08:56.796646Z",
"licenses": [
"agpl"
],
"lastModified": "2016-06-25T16:49:25.319425Z",
"checksum": "909377e1a695bbaa415c10ae087ae1cc48e88066d20a5a7a8beed149e9fad3d5"
}
],
"licenses": [
{
"id": "agpl",
"name": "AGPLv3+"
}
],
"screenshots": [
{
"url": "http://feeds2.feedburner.com/blogerator"
"url": "https://example.com/news.jpg"
}
],
"translations": {
"en": {
"name": "News",
"description": "Read News"
},
"de": {
"name": "Neuigkeiten",
"description": "Nachrichten lesen"
"description": "An RSS/Atom feed reader"
}
}
},
"recommendations": 0,
"featured": false
}
]
translations
Translated fields are stored inside a translations object. They can have any size, depending on if there is a translation. If a required language is not found, you should fall back to English.
screenshots
Guaranteed to be HTTPS
download
Download archive location, guaranteed to be HTTPS
versionSpec
Required versions (minimum and maximum versions) are transformed to semantic version specs. If a field is a \*, this means that there is no version requirement. The following permutations can occur:
@ -141,6 +149,106 @@ recommendations
featured
Simple boolean flag which will be presented to the user as "hey take a look at this app". Does not imply that it has been reviewed or we recommend it officially
categories
The string value is the category's id attribute, see :ref:`api-all-categories`
.. _api-all-categories:
Get All Categories
~~~~~~~~~~~~~~~~~~
This route will return all categories and their translations.
* **Url**: GET /api/v1/categories.json
* **Authentication**: None
* **Caching**: `ETag <https://en.wikipedia.org/wiki/HTTP_ETag>`_
* **Example CURL request**::
curl http://localhost:8000/api/v1/categories.json -H 'If-None-Match: "4-2016-06-11 10:37:24+00:00"'
* **Returns**: application/json
.. code-block:: json
[
{
"id": "games",
"translations": {
"en": {
"name": "Games",
"description": ""
},
"de": {
"name": "Spiele",
"description": ""
},
"fr": {
"name": "Jeux",
"description": ""
}
}
},
{
"id": "multimedia",
"translations": {
"en": {
"name": "Multimedia",
"description": ""
},
"de": {
"name": "Multimedia",
"description": ""
},
"fr": {
"name": "Multimedia",
"description": ""
}
}
},
{
"id": "pim",
"translations": {
"en": {
"name": "PIM",
"description": ""
},
"de": {
"name": "PIM",
"description": ""
},
"fr": {
"name": "PIM",
"description": ""
}
}
},
{
"id": "tools",
"translations": {
"en": {
"name": "Tools",
"description": ""
},
"de": {
"name": "Werkzeuge",
"description": ""
},
"fr": {
"name": "Outil",
"description": ""
}
}
}
]
translations
Translated fields are stored inside a translations object. They can have any size, depending on if there is a translation. If a required language is not found, you should fall back to English.
.. _api-delete-app:
Delete an App

Просмотреть файл

@ -20,23 +20,16 @@ class PhpExtensionDependencySerializer(serializers.ModelSerializer):
class DatabaseDependencySerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField(source='database.id')
name = serializers.ReadOnlyField(source='database.name')
version_spec = SerializerMethodField()
class Meta:
model = DatabaseDependency
fields = ('id', 'name', 'version_spec')
fields = ('id', 'version_spec')
def get_version_spec(self, obj):
return obj.version_spec.replace(',', ' ')
class LicenseSerializer(serializers.ModelSerializer):
class Meta:
model = License
fields = ('id', 'name')
class CategorySerializer(TranslatableModelSerializer):
translations = TranslatedFieldsField(shared_model=Category)
@ -51,7 +44,6 @@ class AppReleaseSerializer(serializers.ModelSerializer):
php_extensions = \
PhpExtensionDependencySerializer(many=True, read_only=True,
source='phpextensiondependencies')
licenses = LicenseSerializer(many=True, read_only=True)
php_version_spec = SerializerMethodField()
platform_version_spec = SerializerMethodField()
@ -77,7 +69,6 @@ class ScreenshotSerializer(serializers.ModelSerializer):
class AppSerializer(serializers.ModelSerializer):
categories = CategorySerializer(many=True, read_only=True)
releases = AppReleaseSerializer(many=True, read_only=True)
screenshots = ScreenshotSerializer(many=True, read_only=True)
translations = TranslatedFieldsField(shared_model=App)

Просмотреть файл

@ -1,6 +1,7 @@
from django.conf.urls import url
from django.views.decorators.http import etag
from nextcloudappstore.core.api.v1.views import Apps, AppReleases, app_api_etag
from nextcloudappstore.core.api.v1.views import Apps, AppReleases, \
app_api_etag, Categories, category_api_etag
urlpatterns = [
url(r'^platform/(?P<version>\d+\.\d+\.\d+)/apps\.json$',
@ -10,4 +11,6 @@ urlpatterns = [
url(r'^apps/(?P<pk>[a-z_]+)/?$', Apps.as_view(), name='app-delete'),
url(r'^apps/(?P<app>[a-z_]+)/releases/(?P<version>\d+\.\d+\.\d+)/?$',
AppReleases.as_view(), name='app-release-delete'),
url(r'^categories.json$',
etag(category_api_etag)(Categories.as_view()), name='categories'),
]

Просмотреть файл

@ -3,15 +3,15 @@ from django.http import Http404
from nextcloudappstore.core.api.v1.release.importer import ReleaseImporter
from nextcloudappstore.core.api.v1.release.provider import AppReleaseProvider
from nextcloudappstore.core.api.v1.serializers import AppSerializer, \
AppReleaseDownloadSerializer
AppReleaseDownloadSerializer, CategorySerializer
from django.db.models import Max, Count
from nextcloudappstore.core.models import App, AppRelease
from nextcloudappstore.core.models import App, AppRelease, Category
from nextcloudappstore.core.permissions import UpdateDeletePermission
from nextcloudappstore.core.throttling import PostThrottle
from pymple import Container
from rest_framework import authentication # type: ignore
from rest_framework.generics import DestroyAPIView, \
get_object_or_404 # type: ignore
get_object_or_404, ListAPIView # type: ignore
from rest_framework.permissions import IsAuthenticated # type: ignore
from rest_framework.response import Response # type: ignore
from semantic_version import Version, Spec
@ -39,6 +39,21 @@ def app_api_etag(request, version):
return '%s-%s' % (count, release_modified)
def category_api_etag(request):
category_aggr = Category.objects.aggregate(count=Count('*'),
modified=Max('last_modified'))
category_modified = category_aggr['modified']
if category_modified is None:
return None
else:
return '%s-%s' % (category_aggr['count'], category_modified)
class Categories(ListAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
class Apps(DestroyAPIView):
authentication_classes = (authentication.BasicAuthentication,)
permission_classes = (UpdateDeletePermission,)
@ -46,13 +61,9 @@ class Apps(DestroyAPIView):
queryset = App.objects.all()
def get(self, request, *args, **kwargs):
apps = App.objects.prefetch_related('translations',
'categories__translations',
'categories', 'releases',
'screenshots',
'releases__databases',
apps = App.objects.prefetch_related('translations', 'screenshots',
'releases', 'releases__databases',
'releases__php_extensions').all()
platform_version = Version(self.kwargs['version'])
def app_filter(app):