From e03e50b4e4d1a48d05ab48fb01769c35fe92d722 Mon Sep 17 00:00:00 2001 From: Nikolay Zamotaev Date: Tue, 5 Jun 2018 16:07:05 +0300 Subject: [deployment-server] Add tag support and filtering Added tag support. Tags are parsed from packages: * extraMetadata * extraSignedMetadata Added parsing requires/conflicts tags from hello phase (Request goes in hello, filtering goes in app list) Change-Id: Ifab174bc3df0dbf4efa4832bb28bb4f58a6a302c Reviewed-by: Dominik Holland --- README.md | 3 +++ store/admin.py | 15 +++++++++++++-- store/api.py | 29 +++++++++++++++++++++++++++-- store/migrations/0001_initial.py | 1 + store/models.py | 2 ++ store/utilities.py | 7 +++++++ 6 files changed, 53 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9c32719..2b0aec9 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,8 @@ Checks whether you are using the right Platform and the right API to communicate | ---------- | ----------- | | `platform` | The platform the client is running on, this sets the architecture of the packages you get. (see `settings.APPSTORE_PLATFORM`) | | `version` | The Deployment Server HTTP API version you are using to communicate with the server. (see `settings.APPSTORE_VERSION`) | +| `require_tag` | Optional parameter for filtering packages by tags. Receives coma-separated list of tags. Only applications containing any of specified tags should be listed. Tags must be alphanumeric. | +| `conflicts_tag` | Optional parameter for filtering packages by tags. Receives coma-separated list of tags. No application containing any of the tags should be listed. Tags must be alphanumeric. | Returns a JSON object: @@ -148,6 +150,7 @@ Returns a JSON object: | | `maintenance` | The Server is in maintenance mode and can't be used at the moment. | | | `incompatible-platform` | You are using an incompatible Platform. | | | `incompatible-version` | You are using an incompatible Version of the API. | +| | `malformed-tag` | Tag had wrong format, was not alphanumeric or could not be parsed. | ## login Does a login on the Server with the given username and password. Either a imei or a mac must be provided. This call is needed for downloading apps. diff --git a/store/admin.py b/store/admin.py index db2672b..0a34951 100644 --- a/store/admin.py +++ b/store/admin.py @@ -118,9 +118,10 @@ class CategoryAdmin(admin.ModelAdmin): return redirect('admin:store_category_changelist') + class AppAdminForm(forms.ModelForm): class Meta: - exclude = ["id", "name"] + exclude = ["id", "name", "tags"] appId = "" name = "" @@ -175,12 +176,22 @@ class AppAdminForm(forms.ModelForm): m = super(AppAdminForm, self).save(commit); m.id = self.appId m.name = self.name + + m.file.seek(0) + pkgdata = parseAndValidatePackageMetadata(m.file) + taglist = set() + for fields in ('extra','extraSigned'): + if fields in pkgdata['header']: + if 'tags' in pkgdata['header'][fields]: + tags = set(pkgdata['header'][fields]['tags']) #Fill tags list then add them + taglist = taglist.union(tags) + m.tags = ",".join(taglist) return m class AppAdmin(admin.ModelAdmin): form = AppAdminForm - list_display = ('name',) + list_display = ('name', 'id') def save_model(self, request, obj, form, change): obj.save() diff --git a/store/api.py b/store/api.py index 5e1620e..9bb8dc2 100644 --- a/store/api.py +++ b/store/api.py @@ -36,12 +36,13 @@ import shutil import json from django.conf import settings +from django.db.models import Q from django.http import HttpResponse, HttpResponseForbidden, Http404, JsonResponse from django.contrib import auth from django.template import Context, loader from models import App, Category, Vendor -from utilities import parsePackageMetadata, packagePath, iconPath, downloadPath, addSignatureToPackage +from utilities import parsePackageMetadata, packagePath, iconPath, downloadPath, addSignatureToPackage, validateTag def hello(request): @@ -54,6 +55,14 @@ def hello(request): elif request.REQUEST.get("version", "") != str(settings.APPSTORE_PLATFORM_VERSION): status = 'incompatible-version' + for j in ("require_tag", "conflicts_tag",): + if j in request.REQUEST: #Tags are coma-separated, + taglist = [i.lower() for i in request.REQUEST[j].split(',') if i] + for i in taglist: + if not validateTag(i): #Tags must be alphanumeric (or, even better - limited to ASCII alphanumeric) + status = 'malformed-tag' + break + request.session[j] = taglist return JsonResponse({'status': status}) @@ -101,11 +110,27 @@ def appList(request): if catId != -1: # All metacategory apps = apps.filter(category__exact = catId) - appList = list(apps.values('id', 'name', 'vendor__name', 'briefDescription', 'category')) + #Tag filtering + #"require_tag", "conflicts_tag" + # Tags are combined by logical AND (for require) and logical OR for conflicts + if 'require_tag' in request.session: + for i in request.session['require_tag']: + regex = '(^|,)%s(,|$)' % (i,) + apps = apps.filter(Q(tags__regex = regex)) + if 'conflicts_tag' in request.session: + for i in request.session['conflicts_tag']: + regex = '(^|,)%s(,|$)' % (i,) + apps = apps.filter(~Q(tags__regex = regex)) + + appList = list(apps.values('id', 'name', 'vendor__name', 'briefDescription', 'category', 'tags').order_by('id')) for app in appList: app['category_id'] = app['category'] app['category'] = Category.objects.all().filter(id__exact = app['category_id'])[0].name app['vendor'] = app['vendor__name'] + if app['tags'] != "": + app['tags'] = app['tags'].split(',') + else: + app['tags'] = [] del app['vendor__name'] # this is not valid JSON, since we are returning a list! diff --git a/store/migrations/0001_initial.py b/store/migrations/0001_initial.py index 3c85e9d..474d3af 100644 --- a/store/migrations/0001_initial.py +++ b/store/migrations/0001_initial.py @@ -54,6 +54,7 @@ class Migration(migrations.Migration): ('description', models.TextField()), ('dateAdded', models.DateField(auto_now_add=True)), ('dateModified', models.DateField(auto_now=True)), + ('tags', models.TextField(blank=True)), ], options={ }, diff --git a/store/models.py b/store/models.py index 4d87f70..7335ab2 100644 --- a/store/models.py +++ b/store/models.py @@ -122,6 +122,7 @@ class App(models.Model): description = models.TextField() dateAdded = models.DateField(auto_now_add = True) dateModified = models.DateField(auto_now = True) + tags = models.TextField(blank=True) def __unicode__(self): return self.name + " [" + self.id + "]" @@ -134,3 +135,4 @@ class App(models.Model): except: pass super(App, self).save(*args, **kwargs) + diff --git a/store/utilities.py b/store/utilities.py index 1b68f6a..a464507 100644 --- a/store/utilities.py +++ b/store/utilities.py @@ -45,6 +45,13 @@ from OpenSSL.crypto import load_pkcs12, FILETYPE_PEM, dump_privatekey, dump_cert from django.conf import settings +def validateTag(tag): + for i in tag: + if not i.isalnum(): + if i != "_": + return False + return True + def packagePath(appId = None): path = settings.MEDIA_ROOT + 'packages/' if appId is not None: -- cgit v1.2.3