fix download function

storage_cloud
nineven 5 years ago
parent f9de02f139
commit 7147e09335
  1. 16
      fir_client/src/components/FirAppInfostimeline.vue
  2. 38
      fir_client/src/components/FirDownload.vue
  3. 22
      fir_client/src/restful/index.js
  4. 4
      fir_ser/api/urls.py
  5. 57
      fir_ser/api/utils/serializer.py
  6. 2
      fir_ser/api/utils/storage/aliyunApi.py
  7. 4
      fir_ser/api/utils/storage/localApi.py
  8. 2
      fir_ser/api/utils/storage/qiniuApi.py
  9. 4
      fir_ser/api/utils/storage/storage.py
  10. 4
      fir_ser/api/views/apps.py
  11. 168
      fir_ser/api/views/download.py
  12. 5
      fir_ser/api/views/login.py
  13. 3
      fir_ser/fir_ser/urls.py

@ -121,7 +121,7 @@
</template> </template>
<script> <script>
import {deletereleaseapp, getapptimeline, updatereleaseapp} from "../restful" import {deletereleaseapp, getapptimeline, getdownloadurl, updatereleaseapp} from "../restful"
export default { export default {
name: "FirAppInfostimeline", name: "FirAppInfostimeline",
@ -138,7 +138,19 @@
}, },
methods: { methods: {
downloadPackage(app){ downloadPackage(app){
window.location.href=app.download_url; getdownloadurl(res=>{
if(res.code === 1000){
window.location.href=res.data.download_url;
}
}, {
'data': {
'token': app.download_token,
'short': this.currentapp.short,
'release_id': app.release_id,
"isdownload":true
},
'app_id': this.currentapp.app_id
})
}, },
previewRelase(app){ previewRelase(app){
this.$router.push({name: 'FirDownload', params: { short: this.currentapp.short },query:{release_id:app.release_id}}) this.$router.push({name: 'FirDownload', params: { short: this.currentapp.short },query:{release_id:app.release_id}})

@ -63,7 +63,7 @@
<button v-if="isdownload" disabled="" class="loading" style="min-width: 43px; width: 43px; padding: 12px 0; border-top-color: transparent; border-left-color: transparent;">&nbsp;</button> <button v-if="isdownload" disabled="" class="loading" style="min-width: 43px; width: 43px; padding: 12px 0; border-top-color: transparent; border-left-color: transparent;">&nbsp;</button>
<button v-else @click="download" > <button v-else @click="download" >
<el-link icon="el-icon-loadings" :href="downloadurl" type="primary" :underline="false"> 下载安装 </el-link> <el-link icon="el-icon-loadings" type="primary" :underline="false"> 下载安装 </el-link>
</button> </button>
</div> </div>
@ -143,7 +143,7 @@
<script> <script>
import QRCode from 'qrcodejs2' import QRCode from 'qrcodejs2'
import {getDownloadToken} from '../restful' import {getShortAppinfo,getdownloadurl} from '../restful'
export default { export default {
name: "FirDownload", name: "FirDownload",
@ -159,13 +159,33 @@
wrong: false, wrong: false,
msg: '', msg: '',
dchoice:false, dchoice:false,
downloadtoken:"",
downloadurl:"", downloadurl:"",
isdownload:false, isdownload:false,
} }
}, methods: { }, methods: {
download() { download() {
this.isdownload = true; this.isdownload = true;
getdownloadurl(res=>{
if(res.code === 1000){
if(this.currentappinfo.type === 1){
this.downloadurl="itms-services://?action=download-manifest&url="+res.data.download_url;
}else{
if(this.agent !== ''){
this.downloadurl = res.data.download_url;
}
}
window.location.href=this.downloadurl;
}
}, {
'data': {
'token': this.mcurrentappinfo.download_token,
'short': this.currentappinfo.short,
'release_id': this.mcurrentappinfo.release_id,
},
'app_id': this.currentappinfo.app_id
})
}, },
qrcode() { qrcode() {
new QRCode('qrcode', { new QRCode('qrcode', {
@ -179,9 +199,8 @@
if(this.$route.query.release_id){ if(this.$route.query.release_id){
params["release_id"]=this.$route.query.release_id params["release_id"]=this.$route.query.release_id
} }
getDownloadToken(data => { getShortAppinfo(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.downloadtoken = data.data.download_token;
if(!data.data.master_release.release_id){ if(!data.data.master_release.release_id){
this.$message({ this.$message({
message:"该 release 版本不存在,请检查", message:"该 release 版本不存在,请检查",
@ -253,15 +272,6 @@
this.miscomboappinfo={}; this.miscomboappinfo={};
this.iscomboappinfo = {}; this.iscomboappinfo = {};
} }
this.downloadurl = this.mcurrentappinfo.download_url;
if(this.currentappinfo.type ===1){
let ios_plist="&type=resigned.plist";
this.downloadurl="itms-services://?action=download-manifest&url="+this.downloadurl+ios_plist
}
if(this.mcurrentappinfo.binary_url && this.agent !== ''){
this.downloadurl=this.mcurrentappinfo.binary_url;
window.location.href=this.mcurrentappinfo.binary_url;
}
} }
else { else {

@ -543,11 +543,11 @@ export function getapptimeline(callBack, params, load = true) {
} }
/**获取下载token */ /**根据短链接获取应用信息 */
export function getDownloadToken(callBack, params, load = true) { export function getShortAppinfo(callBack, params, load = true) {
getData( getData(
false, false,
USERSEVER + '/download_token/' + params.short , USERSEVER + '/short/' + params.short ,
params, params,
data => { data => {
callBack(data); callBack(data);
@ -597,6 +597,22 @@ export function getuploadToken(callBack, params, load = true) {
); );
} }
/**获取下载的url */
export function getdownloadurl(callBack, params, load = true) {
getData(
false,
USERSEVER + '/install/'+params.app_id ,
params.data,
data => {
callBack(data);
},
load,
true,
true
);
}
export function uploadstorage(certinfo,file,successCallback,processCallback) { export function uploadstorage(certinfo,file,successCallback,processCallback) {
let config = { let config = {

@ -21,7 +21,7 @@ from api.views.logout import LogoutView
from api.views.captcha import CaptchaView from api.views.captcha import CaptchaView
from api.views.apps import AppsView,AppInfoView,AppReleaseinfoView from api.views.apps import AppsView,AppInfoView,AppReleaseinfoView
from api.views.localuploads import UploadView,UploadImgView from api.views.localuploads import UploadView,UploadImgView
from api.views.download import DownloadTokenView from api.views.download import ShortDownloadView
from api.views.uploads import OssUploadView from api.views.uploads import OssUploadView
@ -39,6 +39,6 @@ urlpatterns = [
re_path("^upload$",UploadView.as_view()), re_path("^upload$",UploadView.as_view()),
re_path("^img/upload$",UploadImgView.as_view()), re_path("^img/upload$",UploadImgView.as_view()),
re_path("^userinfo",UserInfoView.as_view()), re_path("^userinfo",UserInfoView.as_view()),
re_path("^download_token/(?P<short>\w+)$", DownloadTokenView.as_view()), re_path("^short/(?P<short>\w+)$", ShortDownloadView.as_view()),
re_path("^qiniuupload$", OssUploadView.as_view()), re_path("^qiniuupload$", OssUploadView.as_view()),
] ]

@ -1,11 +1,11 @@
from rest_framework import serializers from rest_framework import serializers
from api import models from api import models
from api.utils.app.apputils import bytes2human from api.utils.app.apputils import bytes2human
from api.utils.storage.storage import Storage from api.utils.TokenManager import DownloadToken
import os import os
token_obj = DownloadToken()
class UserInfoSerializer(serializers.ModelSerializer): class UserInfoSerializer(serializers.ModelSerializer):
# gender = serializers.SerializerMethodField(source="get_gender_display") # gender = serializers.SerializerMethodField(source="get_gender_display")
@ -45,7 +45,7 @@ class AppsSerializer(serializers.ModelSerializer):
release_id=self.context.get("release_id")).first() release_id=self.context.get("release_id")).first()
if master_release_obj: if master_release_obj:
icon_url = "/".join([obj.user_id.domain_name, master_release_obj.icon_url]) icon_url = ""
if self.context.get("storage", None) and self.context.get("storage") != "undefined": if self.context.get("storage", None) and self.context.get("storage") != "undefined":
storage = self.context.get("storage", None) storage = self.context.get("storage", None)
@ -63,35 +63,30 @@ class AppsSerializer(serializers.ModelSerializer):
"binary_url":master_release_obj.binary_url, "binary_url":master_release_obj.binary_url,
} }
if self.context.get("download_token", None) and self.context.get("download_token") != "download_token": download_url = ""
download_url = "/".join([obj.user_id.domain_name, "download", master_release_obj.release_id]) # if self.context.get("storage", None) and self.context.get("storage") != "undefined":
download_url = download_url + "?token=" + self.context.get("download_token") # if obj.type == 0:
if obj.type == 0: # apptype = '.apk'
apptype='.apk' # else:
else: # apptype = '.ipa'
apptype = '.ipa' # storage = self.context.get("storage", None)
if self.context.get("storage", None) and self.context.get("storage") != "undefined": # download_url = storage.get_download_url(master_release_obj.release_id+apptype)
storage = self.context.get("storage", None) download_token = token_obj.make_token(master_release_obj.release_id,600)
download_url = storage.get_download_url(master_release_obj.release_id+apptype) datainfo["download_token"] = download_token
datainfo["download_url"] = download_url
return datainfo return datainfo
else: else:
return {} return {}
class AppReleaseSerializer(serializers.ModelSerializer): class AppReleaseSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.AppReleaseInfo model = models.AppReleaseInfo
fields = ["app_version", "icon_url", "build_version", fields = ["app_version", "icon_url", "build_version",
"release_type", "minimum_os_version", "release_type", "minimum_os_version",
"created_time", "binary_size", "release_id", "size", "type", "editing", "master_color", "changelog", "created_time", "binary_size", "release_id", "size", "type", "editing", "master_color", "changelog",
"is_master",'download_url','binary_url'] "is_master",'download_token','binary_url']
download_url = serializers.SerializerMethodField() download_token = serializers.SerializerMethodField()
size = serializers.SerializerMethodField() size = serializers.SerializerMethodField()
type = serializers.SerializerMethodField() type = serializers.SerializerMethodField()
editing = serializers.SerializerMethodField() editing = serializers.SerializerMethodField()
@ -104,19 +99,19 @@ class AppReleaseSerializer(serializers.ModelSerializer):
def get_binary_size(self,obj): def get_binary_size(self,obj):
return bytes2human(obj.binary_size) return bytes2human(obj.binary_size)
def get_download_url(self,obj): def get_download_token(self,obj):
# download_url = "/".join([obj.app_id.user_id.domain_name, "download", obj.release_id])
# download_url = download_url + "?token=" + make_download_token(obj.release_id,300)
download_url='' download_url=''
if obj.release_type == 0: # if obj.release_type == 0:
apptype = '.apk' # apptype = '.apk'
else: # else:
apptype = '.ipa' # apptype = '.ipa'
if self.context.get("storage", None) and self.context.get("storage") != "undefined": # if self.context.get("storage", None) and self.context.get("storage") != "undefined":
storage = self.context.get("storage", None) # storage = self.context.get("storage", None)
download_url = storage.get_download_url(obj.release_id + apptype) # download_url = storage.get_download_url(obj.release_id + apptype)
return download_url download_token = token_obj.make_token(obj.release_id, 600)
return download_token
def get_icon_url(self, obj): def get_icon_url(self, obj):
icon_url="/".join([obj.app_id.user_id.domain_name, obj.icon_url]) icon_url="/".join([obj.app_id.user_id.domain_name, obj.icon_url])

@ -98,7 +98,7 @@ class AliYunOss(object):
auth = oss2.StsAuth(self.token.access_key_id, self.token.access_key_secret, self.token.security_token) auth = oss2.StsAuth(self.token.access_key_id, self.token.access_key_secret, self.token.security_token)
self.bucket = oss2.Bucket(auth, self.endpoint,self.bucket_name) self.bucket = oss2.Bucket(auth, self.endpoint,self.bucket_name)
def get_download_url(self,name,expires=1800): def get_download_url(self,name,expires=1800,ftype=None):
# self.get_auth_bucket(name,expires) # self.get_auth_bucket(name,expires)
private_url=self.bucket.sign_url('GET',name,expires) private_url=self.bucket.sign_url('GET',name,expires)
print(private_url) print(private_url)

@ -18,10 +18,12 @@ class LocalStorage(object):
dtoken = DownloadToken() dtoken = DownloadToken()
return dtoken.make_token(name,expires) return dtoken.make_token(name,expires)
def get_download_url(self,name,expires=1800): def get_download_url(self,name,expires=1800,ftype=None):
dtoken = DownloadToken() dtoken = DownloadToken()
base_url = '/'.join([self.domain_name,'download', name]) base_url = '/'.join([self.domain_name,'download', name])
download_url = base_url + "?token=" + dtoken.make_token(name,expires) download_url = base_url + "?token=" + dtoken.make_token(name,expires)
if ftype:
download_url = download_url + '&ftype=' + ftype
return download_url return download_url
def del_file(self,name): def del_file(self,name):

@ -31,7 +31,7 @@ class QiNiuOss(object):
# print(token) # print(token)
return token return token
def get_download_url(self,name,expires=1800): def get_download_url(self,name,expires=1800,ftype=None):
#有两种方式构造base_url的形式 #有两种方式构造base_url的形式
base_url = '%s/%s' % (self.domain_name, name) base_url = '%s/%s' % (self.domain_name, name)
#或者直接输入url的方式下载 #或者直接输入url的方式下载

@ -19,9 +19,9 @@ class Storage(object):
if self.storage: if self.storage:
return self.storage.get_upload_token(filename, expires) return self.storage.get_upload_token(filename, expires)
def get_download_url(self, filename, expires=1800): def get_download_url(self, filename, expires=1800,ftype=None):
if self.storage: if self.storage:
return self.storage.get_download_url(filename, expires) return self.storage.get_download_url(filename, expires,ftype)
def delete_file(self, filename, apptype=None): def delete_file(self, filename, apptype=None):
if self.storage: if self.storage:

@ -16,7 +16,6 @@ from api.utils.storage.storage import Storage
from api.models import Apps, AppReleaseInfo from api.models import Apps, AppReleaseInfo
from api.utils.serializer import AppsSerializer, AppReleaseSerializer, UserInfoSerializer from api.utils.serializer import AppsSerializer, AppReleaseSerializer, UserInfoSerializer
from api.utils.app.apputils import delete_apps_icon_storage
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
@ -206,7 +205,8 @@ class AppInfoView(APIView):
random_file_name) random_file_name)
release_obj.save() release_obj.save()
delete_apps_icon_storage(old_file_name, 'icons') storage = Storage(request.user)
storage.delete_file(old_file_name)
else: else:
res.code = 1003 res.code = 1003

@ -8,6 +8,9 @@ from api.utils.response import BaseResponse
from rest_framework.response import Response from rest_framework.response import Response
from fir_ser import settings from fir_ser import settings
from api.utils.TokenManager import DownloadToken from api.utils.TokenManager import DownloadToken
from api.utils.app.randomstrings import make_random_uuid
from api.utils.app.apputils import make_resigned
from api.utils.storage.storage import Storage
import os import os
from api.utils.serializer import AppsSerializer from api.utils.serializer import AppsSerializer
@ -15,18 +18,15 @@ from api.models import Apps,AppReleaseInfo
from django.http import FileResponse from django.http import FileResponse
class DownloadView(APIView): class DownloadView(APIView):
''' '''
文件下载接口 文件下载接口,适用于本地存储和所有plist文件下载
''' '''
# authentication_classes = [ExpiringTokenAuthentication, ] # authentication_classes = [ExpiringTokenAuthentication, ]
# parser_classes = (MultiPartParser,) # parser_classes = (MultiPartParser,)
def get(self,request,filename): def get(self,request,filename):
# release_id = release_id.split(".")[1]
res = BaseResponse() res = BaseResponse()
print(filename)
downtoken = request.query_params.get("token", None) downtoken = request.query_params.get("token", None)
iostype = request.query_params.get("type",None) ftype = request.query_params.get("ftype",None)
if not downtoken: if not downtoken:
res.code=1004 res.code=1004
res.msg="缺失token" res.msg="缺失token"
@ -34,102 +34,98 @@ class DownloadView(APIView):
dtoken = DownloadToken() dtoken = DownloadToken()
if dtoken.verify_token(downtoken,filename): if dtoken.verify_token(downtoken,filename):
file_path = os.path.join(settings.MEDIA_ROOT, filename) if not ftype:
try: file_path = os.path.join(settings.MEDIA_ROOT, filename)
response = FileResponse(open(file_path, 'rb')) try:
except Exception as e: response = FileResponse(open(file_path, 'rb'))
print(e) except Exception as e:
response = FileResponse() print(e)
response['content_type'] = "application/octet-stream" response = FileResponse()
response['Content-Disposition'] = 'attachment; filename=' + filename response['content_type'] = "application/octet-stream"
return response response['Content-Disposition'] = 'attachment; filename=' + filename
else: return response
res.code=1004 else:
res.msg="token校验失败" if ftype == 'plist':
return Response(res.dict) release_obj = AppReleaseInfo.objects.filter(release_id=filename.split('.')[0]).first()
if release_obj:
storage = Storage(release_obj.app_id.user_id)
bundle_id = release_obj.app_id.bundle_id
bundle_version = release_obj.build_version
name = release_obj.app_id.name
ios_plist_bytes = make_resigned(storage.get_download_url(filename),storage.get_download_url(release_obj.icon_url),bundle_id,bundle_version,name)
response = FileResponse(ios_plist_bytes)
response['content_type'] = "application/x-plist"
response['Content-Disposition'] = 'attachment; filename=' + make_random_uuid()
return response
res.code=1004
res.msg="token校验失败"
return Response(res.dict)
# #校验成功,可以下载数据 class ShortDownloadView(APIView):
# apprelease_obj = AppReleaseInfo.objects.filter(release_id=release_id).first()
# if apprelease_obj:
# try:
# app_type="ipa"
# if apprelease_obj.release_type == 0:
# app_type = "apk"
#
# if iostype == "resigned.plist":
# file_path=make_random_uuid()
#
# downtoken=dtoken.make_token([release_id])
# domain_name = apprelease_obj.app_id.user_id.domain_name
# if not domain_name:
# ser_url = request.META.get("HTTP_HOST", "/")
# SERVER_PROTOCOL = request.META.get("SERVER_PROTOCOL", "http/1.1")
# ser_protocol = SERVER_PROTOCOL.split("/")[0].lower()
# domain_name = "%s://%s" % (ser_protocol, ser_url)
# bin_url = domain_name+"/download/"+\
# release_id+"?token="+\
# downtoken
# img_url=apprelease_obj.icon_url
# bundle_id = apprelease_obj.app_id.bundle_id
# bundle_version = apprelease_obj.build_version
# name = apprelease_obj.app_id.name
# ios_plist_bytes = make_resigned(bin_url,img_url,bundle_id,bundle_version,name)
# response = FileResponse(ios_plist_bytes)
# response['content_type'] = "text/xml"
#
#
# else:
#
# apprelease_obj.app_id.count_hits+=1
# apprelease_obj.app_id.save()
#
# file_path = os.path.join(settings.MEDIA_ROOT,"apps","%s"%(release_id+"."+app_type))
# response = FileResponse(open(file_path, 'rb'))
# response['content_type'] = "application/octet-stream"
# response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_path)
# return response
# except Exception:
# raise Http404
# pass
# else:
# res.code=1004
# res.msg="token校验失败"
# return Response(res.dict)
class DownloadTokenView(APIView):
''' '''
获取下载的token信息 根据下载短链接获取应用信息
''' '''
# authentication_classes = [ExpiringTokenAuthentication, ]
# parser_classes = (MultiPartParser,)
def get(self,request,short): def get(self,request,short):
res = BaseResponse() res = BaseResponse()
release_ids=[]
release_id = request.query_params.get("release_id", None) release_id = request.query_params.get("release_id", None)
if release_id:
release_ids = [release_id]
# res.data["release_id"] = release_id
dtoken = DownloadToken()
app_obj = Apps.objects.filter(short=short).first() app_obj = Apps.objects.filter(short=short).first()
# release_id = AppReleaseInfo.objects.filter(app_id=app_obj).first().release_id
if not app_obj: if not app_obj:
res.code=1003 res.code=1003
res.msg="该应用不存在" res.msg="该应用不存在"
return Response(res.dict) return Response(res.dict)
app_release_obj = AppReleaseInfo.objects.filter(app_id=app_obj,is_master=True).first() app_serializer = AppsSerializer(app_obj,context={"release_id":release_id,"storage":Storage(app_obj.user_id)})
release_ids.append(app_release_obj.release_id)
if app_obj.has_combo:
release_ids.append(AppReleaseInfo.objects.filter(app_id=app_obj.has_combo,is_master=True).first().release_id)
download_token = dtoken.make_token(release_ids)
app_serializer = AppsSerializer(app_obj,context={"release_id":release_id,"download_token":download_token})
res.data = app_serializer.data res.data = app_serializer.data
res.data["download_token"]=download_token
return Response(res.dict) return Response(res.dict)
class InstallView(APIView):
'''
安装操作前端通过token 认证认证成功之后返回 下载连接并且 让下载次数加一
'''
def get(self,request,app_id):
res = BaseResponse()
downtoken = request.query_params.get("token", None)
short = request.query_params.get("short",None)
release_id = request.query_params.get("release_id",None)
isdownload = request.query_params.get("isdownload",None)
if not downtoken or not short or not release_id:
res.code=1004
res.msg="参数丢失"
return Response(res.dict)
dtoken = DownloadToken()
if dtoken.verify_token(downtoken,release_id):
app_obj = Apps.objects.filter(app_id=app_id,short=short).first()
if app_obj:
app_obj.count_hits = app_obj.count_hits+1
app_obj.save()
release_obj = AppReleaseInfo.objects.filter(app_id=app_obj,release_id=release_id).first()
if release_obj:
storage = Storage(app_obj.user_id)
if app_obj.type == 0:
apptype = '.apk'
download_url = storage.get_download_url(release_obj.release_id + apptype,600)
else:
apptype = '.ipa'
if isdownload :
download_url = storage.get_download_url(release_obj.release_id + apptype, 600)
else:
download_url = storage.get_download_url(release_obj.release_id + apptype,600,'plist')
res.data={"download_url":download_url}
return Response(res.dict)
else:
res.code = 1004
res.msg = "token校验失败"
return Response(res.dict)
res.code = 1006
res.msg = "该应用不存在"
return Response(res.dict)

@ -11,9 +11,9 @@ import binascii
import os,datetime import os,datetime
from api.utils.TokenManager import DownloadToken,generateNumericTokenOfLength from api.utils.TokenManager import DownloadToken,generateNumericTokenOfLength
from api.utils.auth import ExpiringTokenAuthentication from api.utils.auth import ExpiringTokenAuthentication
from api.utils.app.apputils import delete_apps_icon_storage
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from django.middleware import csrf from django.middleware import csrf
from api.utils.storage.storage import Storage
def get_token(request): def get_token(request):
token = csrf.get_token(request) token = csrf.get_token(request)
@ -158,7 +158,8 @@ class UserInfoView(APIView):
request.user.head_img = head_img request.user.head_img = head_img
request.user.save() request.user.save()
if old_head_img != "" or old_head_img != '/files/imgs/head_img.jpeg': if old_head_img != "" or old_head_img != '/files/imgs/head_img.jpeg':
delete_apps_icon_storage(os.path.basename(old_head_img),'imgs') storage = Storage(request.user)
storage.delete_file(os.path.basename(old_head_img))
except Exception as e: except Exception as e:
res.code = 1003 res.code = 1003

@ -32,7 +32,7 @@ from api.views.captcha import CaptchaView
# router.register("course/category", CourseCategoryView) # router.register("course/category", CourseCategoryView)
from django.views.static import serve from django.views.static import serve
from fir_ser import settings from fir_ser import settings
from api.views.download import DownloadView from api.views.download import DownloadView,InstallView
urlpatterns = [ urlpatterns = [
path('fly.admin/', admin.site.urls), path('fly.admin/', admin.site.urls),
@ -48,4 +48,5 @@ urlpatterns = [
# media路径配置 # media路径配置
re_path(r'files/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), re_path(r'files/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
re_path("download/(?P<filename>\w+\.\w+)$", DownloadView.as_view(),name="download"), re_path("download/(?P<filename>\w+\.\w+)$", DownloadView.as_view(),name="download"),
re_path("install/(?P<app_id>\w+)$", InstallView.as_view(), name="install"),
] ]

Loading…
Cancel
Save