Commit 5ee39d73 by aagusti

penambahan view_next

1 parent 1d304fb0
...@@ -2,6 +2,8 @@ import locale ...@@ -2,6 +2,8 @@ import locale
import logging import logging
import re import re
import colander
try: try:
from urllib import (urlencode, quote, quote_plus, ) from urllib import (urlencode, quote, quote_plus, )
except ImportError: except ImportError:
...@@ -101,6 +103,8 @@ def has_permission_(request, perm_names, context=None): ...@@ -101,6 +103,8 @@ def has_permission_(request, perm_names, context=None):
if request.has_permission(perm_name, context): if request.has_permission(perm_name, context):
return True return True
def _get_params(request, params, default=None, settings=None, context=None):
return get_params(params, default, settings)
@subscriber(BeforeRender) @subscriber(BeforeRender)
def add_global(event): def add_global(event):
...@@ -422,16 +426,12 @@ def main(global_config, **settings): ...@@ -422,16 +426,12 @@ def main(global_config, **settings):
config.add_request_method(is_devel, 'devel', reify=True) config.add_request_method(is_devel, 'devel', reify=True)
config.add_request_method(get_host, '_host', reify=True) config.add_request_method(get_host, '_host', reify=True)
config.add_request_method(get_home, 'home', reify=True) config.add_request_method(get_home, 'home', reify=True)
# config.add_request_method(api_has_permission_, 'api_has_permission', reify=True)
config.add_request_method(google_signin_client_id, config.add_request_method(google_signin_client_id,
'google_signin_client_id', reify=True) 'google_signin_client_id', reify=True)
config.add_request_method(google_signin_client_ids, config.add_request_method(google_signin_client_ids,
'google_signin_client_ids', reify=True) 'google_signin_client_ids', reify=True)
config.add_request_method(allow_register, 'allow_register', reify=True) config.add_request_method(allow_register, 'allow_register', reify=True)
config.add_request_method(disable_responsive, 'disable_responsive', reify=True) config.add_request_method(disable_responsive, 'disable_responsive', reify=True)
# config.add_request_method(get_params, 'get_params', reify=True)
# config.add_request_method(get_ini_params, 'get_ini', reify=True)
config.add_request_method(get_ini, 'get_ini', reify=True) config.add_request_method(get_ini, 'get_ini', reify=True)
config.add_translation_dirs('opensipkd.base:locale/') config.add_translation_dirs('opensipkd.base:locale/')
......
# from pyramid_rpc.amfgateway import PyramidGateway # from pyramid_rpc.amfgateway import PyramidGateway
from pyramid_rpc.jsonrpc import jsonrpc_method from pyramid_rpc.jsonrpc import jsonrpc_method
from opensipkd.tools.api import JsonRpcInvalidDataError, JsonRpcInvalidLoginError from opensipkd.tools.api import JsonRpcInvalidData, JsonRpcInvalidLoginError
from ziggurat_foundations.models.services.user import UserService from ziggurat_foundations.models.services.user import UserService
from opensipkd.models import Partner, User from opensipkd.models import Partner, User
......
...@@ -6,7 +6,6 @@ from datetime import datetime ...@@ -6,7 +6,6 @@ from datetime import datetime
from datatables import ColumnDT from datatables import ColumnDT
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from opensipkd.tools.captcha import get_captcha from opensipkd.tools.captcha import get_captcha
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
...@@ -209,6 +208,9 @@ class BaseView(object): ...@@ -209,6 +208,9 @@ class BaseView(object):
def get_bindings(self, row=None): def get_bindings(self, row=None):
return {} return {}
def next_view(self, form, **kwargs):
return self.route_list()
def view_view(self): # row = query_id(request).first() def view_view(self): # row = query_id(request).first()
request = self.req request = self.req
row = self.query_id().first() row = self.query_id().first()
...@@ -218,14 +220,16 @@ class BaseView(object): ...@@ -218,14 +220,16 @@ class BaseView(object):
form = self.get_form(self.edit_schema, buttons=(btn_close,), form = self.get_form(self.edit_schema, buttons=(btn_close,),
bindings=bindings) bindings=bindings)
if request.POST: if request.POST:
return self.route_list() result = self.next_view(form)
if result:
return result
form.set_appstruct(self.get_values(row)) form.set_appstruct(self.get_values(row))
table = self.get_item_table(row) table = self.get_item_table(row)
return dict(form=form.render(readonly=True), return dict(form=form.render(readonly=True),
table=table and table.render() or None, table=table and table.render() or None,
scripts=self.form_scripts) scripts=self.form_scripts)
def before_add(self): def before_add(self):
return {} return {}
...@@ -254,8 +258,8 @@ class BaseView(object): ...@@ -254,8 +258,8 @@ class BaseView(object):
columns = [] columns = []
for d in self.list_schema(): for d in self.list_schema():
global_search = hasattr(d, "searchable") and \ global_search = hasattr(d, "searchable") and \
hasattr(d, "searchable") == False and False \ hasattr(d, "searchable") == False and False \
or True or True
if hasattr(d, "field"): if hasattr(d, "field"):
if type(d.field) == str: if type(d.field) == str:
columns.append( columns.append(
......
...@@ -139,7 +139,6 @@ ...@@ -139,7 +139,6 @@
window.onload = function (e) { window.onload = function (e) {
const value = document.cookie; const value = document.cookie;
const parts = value.split(`g_state=`); const parts = value.split(`g_state=`);
console.log(parts.length)
if (parts.length === 2) { if (parts.length === 2) {
document.cookie = document.cookie + ";max-age=0"; document.cookie = document.cookie + ";max-age=0";
} }
......
...@@ -8,7 +8,7 @@ from opensipkd.base.views.partner_base import NamaSchema ...@@ -8,7 +8,7 @@ from opensipkd.base.views.partner_base import NamaSchema
from opensipkd.jsonrpc_auth import JsonRpcInvalidLogin from opensipkd.jsonrpc_auth import JsonRpcInvalidLogin
from opensipkd.tools import create_now from opensipkd.tools import create_now
from opensipkd.tools.api import ( from opensipkd.tools.api import (
JsonRpcInvalidLoginError, JsonRpcInvalidDataError, JsonRpcInvalidLoginError, JsonRpcInvalidData,
JsonRpcUserNotFoundError) JsonRpcUserNotFoundError)
from pyramid.i18n import TranslationStringFactory from pyramid.i18n import TranslationStringFactory
from pyramid.security import remember, forget from pyramid.security import remember, forget
...@@ -234,7 +234,7 @@ def set_profile_(request, data): ...@@ -234,7 +234,7 @@ def set_profile_(request, data):
controls = form.validate(controls) controls = form.validate(controls)
except ValidationFailure as e: except ValidationFailure as e:
print(e.error, type(e.error)) print(e.error, type(e.error))
raise JsonRpcInvalidDataError(data=e.error.asdict()) raise JsonRpcInvalidData(data=e.error.asdict())
values = dict(controls) values = dict(controls)
partner = Partner.query().filter_by(email=values["email"]).first() partner = Partner.query().filter_by(email=values["email"]).first()
if not partner: if not partner:
......
...@@ -275,3 +275,50 @@ class CaptchaWidget(Widget): ...@@ -275,3 +275,50 @@ class CaptchaWidget(Widget):
if not pstruct: if not pstruct:
return null return null
return pstruct return pstruct
class ImageWidget(Widget):
"""
Renders an ``<img src="src"/>`` widget.
**Attributes/Arguments**
template
The template name used to render the widget. Default:
``image``.
readonly_template
The template name used to render the widget in read-only mode.
Default: ``readonly/image``.
strip
If true, during deserialization, strip the value of leading
and trailing whitespace (default ``True``).
"""
template = "opensipkd.base:views/widgets/image.pt"
readonly_template = "image"
strip = True
requirements = ()
def __init__(self, **kw):
super().__init__(**kw)
def serialize(self, field, cstruct, **kw):
if cstruct in (null, None):
cstruct = ""
readonly = kw.get("readonly", self.readonly)
template = readonly and self.readonly_template or self.template
values = self.get_template_values(field, cstruct, kw)
return field.renderer(template, **values)
def deserialize(self, field, pstruct):
if pstruct is null:
return null
elif not isinstance(pstruct, string_types):
raise Invalid(field.schema, "Pstruct is not a string")
if self.strip:
pstruct = pstruct.strip()
if not pstruct:
return null
return pstruct
<span tal:define="name name|field.name;
css_class css_class|field.widget.css_class;
oid oid|field.oid;
style style|field.widget.style;
"
tal:omit-tag="">
<img style="height:30px; width:auto; margin-bottom:5px;" src="${cstruct}">
</span>
<span tal:define="name name|field.name;
css_class css_class|field.widget.css_class;
oid oid|field.oid;
style style|field.widget.style;
"
tal:omit-tag="">
<img style="height:30px; width:auto; margin-bottom:5px;" src="${cstruct}">
<input type="text" name="${name}" value=""
tal:attributes="class string: form-control ${css_class or ''};
style style;
attributes|field.widget.attributes|{};"
id="${oid}"/>
<script>
deform.addCallback(
'${oid}',
function (oid) {
$("#" + oid).on('input', function (evt) {
$(this).val(function (_, val) {
return val.toUpperCase();
});
});
});
</script>
</span>
<span tal:define="name name|field.name;
css_class css_class|field.widget.css_class;
oid oid|field.oid;
style style|field.widget.style;
"
tal:omit-tag="">
<img style="height:30px; width:auto; margin-bottom:5px;" src="${cstruct}">
</span>
...@@ -10,6 +10,7 @@ from chameleon.utils import Markup ...@@ -10,6 +10,7 @@ from chameleon.utils import Markup
from deform import compat from deform import compat
from deform import field from deform import field
from . import widget from . import widget
# from deform import widget # from deform import widget
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -129,7 +130,7 @@ class DeTable(field.Field): ...@@ -129,7 +130,7 @@ class DeTable(field.Field):
btn_pdf_js = "{window.open(o%sUri+'/pdf/act%s');}" % (tableid, params) btn_pdf_js = "{window.open(o%sUri+'/pdf/act%s');}" % (tableid, params)
action_suffix = f"{action_suffix}{params}" action_suffix = f"{action_suffix}{params}"
field.Field.__init__(self, schema, **kw) field.Field.__init__(self, schema, **kw)
self.request=kw.get("request") self.request = kw.get("request")
_buttons = [] _buttons = []
for button in buttons: for button in buttons:
if isinstance(button, compat.string_types): if isinstance(button, compat.string_types):
...@@ -179,31 +180,28 @@ class DeTable(field.Field): ...@@ -179,31 +180,28 @@ class DeTable(field.Field):
if hasattr(f, 'visible'): if hasattr(f, 'visible'):
d["visible"] = f.visible d["visible"] = f.visible
data.append(f"visible: {f.visible}") data.append(f"visible: {f.visible}")
if hasattr(f, 'orderable'): if hasattr(f, 'orderable'):
d["orderable"] = f.orderable d["orderable"] = f.orderable
data.append(f"orderable: {f.orderable}") data.append(f"orderable: {f.orderable}")
if hasattr(f, "url"): if hasattr(f, "url"):
request=kw.get("request") request = kw.get("request")
if request: if request:
d["url"]= request.static_url(f.url) d["url"] = request.static_url(f.url)
log.debug(d["url"]) log.debug(d["url"])
if hasattr(f, "action"): if hasattr(f, "action"):
d["action"] = f.action d["action"] = f.action
else: else:
d["action"]=True d["action"] = True
if isinstance(f.widget, deform.widget.HiddenWidget): if isinstance(f.widget, deform.widget.HiddenWidget):
d["visible"] = False d["visible"] = False
if isinstance(f.widget, deform.widget.CheckboxWidget): if isinstance(f.widget, deform.widget.CheckboxWidget):
d["checkbox"] = True d["checkbox"] = True
else: else:
d["checkbox"]=False d["checkbox"] = False
thousand = hasattr(f, 'thousand') and f.thousand or None thousand = hasattr(f, 'thousand') and f.thousand or None
separator = thousand and "separator" in thousand and thousand[ separator = thousand and "separator" in thousand and thousand[
...@@ -213,10 +211,11 @@ class DeTable(field.Field): ...@@ -213,10 +211,11 @@ class DeTable(field.Field):
point = thousand and "point" in thousand and thousand["point"] or 0 point = thousand and "point" in thousand and thousand["point"] or 0
currency = thousand and "currency" in thousand and thousand[ currency = thousand and "currency" in thousand and thousand[
"currency"] or "" "currency"] or ""
if thousand or type(f.typ) == colander.Float() or type( if thousand or isinstance(f.typ, colander.Float) or \
f.typ) == colander.Integer(): isinstance(f.typ, colander.Integer):
d["render"] = f"<script>$.fn.dataTable.render.number( '{separator}', " \ d["render"] = \
f"'{decimal}', {point}, '{currency}' )</script>" f"<script>$.fn.dataTable.render.number( '{separator}', " \
f"'{decimal}', {point}, '{currency}' )</script>"
if 'className' not in d: if 'className' not in d:
d["className"] = "text-right" d["className"] = "text-right"
columns.append(d) columns.append(d)
......
<div <div
tal:define="style style|field.widget.style; tal:define="style style|field.widget.style;
css_class css_class|string:${field.widget.css_class or field.css_class or 'jarviswidget jarviswidget-color-blueLight'}; css_class css_class|string:${field.widget.css_class or field.css_class or 'jarviswidget jarviswidget-color-blueLight'};
item_template item_template|field.widget.item_template; item_template item_template|field.widget.item_template;
title title|field.title; title title|field.title;
errormsg errormsg|field.errormsg; errormsg errormsg|field.errormsg;
description description|field.description; description description|field.description;
buttons buttons|field.buttons; buttons buttons|field.buttons;
tableid tableid|field.tableid; tableid tableid|field.tableid;
columns columns|field.columns; columns columns|field.columns;
btnscripts scripts|field.scripts; btnscripts scripts|field.scripts;
url url|field.url; url url|field.url;
url_suffix url_suffix|field.url_suffix; url_suffix url_suffix|field.url_suffix;
sorts sorts|field.sorts; sorts sorts|field.sorts;
filters filters|field.filters; filters filters|field.filters;
paginates paginates|field.paginates; paginates paginates|field.paginates;
server_side server_side|field.server_side; server_side server_side|field.server_side;
data data|field.data; data data|field.data;
allow_edit allow_edit|field.allow_edit; allow_edit allow_edit|field.allow_edit;
allow_delete allow_delete|field.allow_delete; allow_delete allow_delete|field.allow_delete;
" "
tal:attributes="style style; class css_class; attributes|field.widget.attributes|{};" tal:attributes="style style; class css_class; attributes|field.widget.attributes|{};"
i18n:domain="detable" i18n:domain="detable"
> >
<header role="heading" class="txt-color-grayDark"> <header role="heading" class="txt-color-grayDark">
<h2 tal:condition="title"><i class="fa fa-fw fa-table"></i>${title}</h2> <h2 tal:condition="title"><i class="fa fa-fw fa-table"></i>${title}</h2>
<div role="content"> <div role="content">
<div class="widget-body"> <div class="widget-body">
<table <table
id="${tableid}" id="${tableid}"
class="table table-bordered table-hover table-condensed dataTable no-footer" class="table table-bordered table-hover table-condensed dataTable no-footer"
> >
<thead> <thead>
<tr> <tr>
<th tal:repeat="child field">${child.title}</th> <th tal:repeat="child field">${child.title}</th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>
</table> </table>
...@@ -43,121 +43,118 @@ ...@@ -43,121 +43,118 @@
</header> </header>
<script type="text/javascript"> <script type="text/javascript">
var m${tableid}ID;
var o${tableid};
var o${tableid}Uri = "${url}";
var o${tableid}Url = o${tableid}Uri + "${url_suffix}";
deform.addCallback deform.addCallback
('${tableid}', function (oid) { ('${tableid}', function (oid) {
let m${tableid}ID; // $(document).ready(function () {
let o${tableid};
let o${tableid}Uri = "${url}";
let o${tableid}Url = o${tableid}Uri + "${url_suffix}";
let tb_array = [
'<div class="btn-group pull-left">',
'${structure:buttons}',
' &nbsp;',
'</div>',
]
let columns = ${structure: columns};
function render_checkbox(value) {
if (value == true) {
return '<i class="fas fa-check-square" aria-hidden="true">';
}
return 'Archived';
}
for (let co in columns) { let tb_array = [
console.log(columns[co]); '<div class="btn-group pull-left">',
if (columns[co].checkbox === true) { '${structure:buttons}',
columns[co].className = "text-center"; ' &nbsp;',
columns[co].width = "30pt"; '</div>',
columns[co].render = function (val) { ]
if (["", false, 0].indexOf(val) === -1) { let ${tableid}Columns = ${structure: columns};
return render_checkbox(true);
} else return render_checkbox(false);
}
}else if (columns[co].hasOwnProperty("url")){
let url = columns[co].url;
columns[co].render = function (data) {
let result = '<a href="'+url+'/' + data + '" target="_blank">'+data+'</a>&nbsp;';
return result;
}
}else if(columns[co].data == "id" && columns[co].action == true){
columns[co].render = function (id) {
let result = '<a href="${url}/' + id + '/view"><i class="fas fa-eye" aria-hidden="true"></i></a>&nbsp;';
if (${allow_edit}) {
result += '<a href="${url}/' + id + '/edit"><i class="fas fa-edit" aria-hidden="true"></i></a>&nbsp;'
}
if (${allow_delete}) {
result += '<a href="${url}/' + id + '/delete"><i class="fas fa-trash" aria-hidden="true"></i></a>';
}
return result; function render_checkbox(value) {
} if (value === true) {
columns[co].width = "30pt"; return '<i class="fas fa-check-square" aria-hidden="true">';
columns[co].orderable = false; }
columns[co].className = "text-center"; return 'Archived';
columns[co].visible = true; }
//columns[1].order = "order_asc";
for (let co in ${tableid}Columns) {
if (${tableid}Columns[co].checkbox === true) {
${tableid}Columns[co].className = "text-center";
${tableid}Columns[co].width = "30pt";
${tableid}Columns[co].render = function (val) {
if (["", false, 0].indexOf(val) === -1) {
return render_checkbox(true);
} else return render_checkbox(false);
}
} else if (${tableid}Columns[co].hasOwnProperty("url")) {
let url = ${tableid}Columns[co].url;
${tableid}Columns[co].render = function (data) {
let result = '<a href="' + url + '/' + data + '" target="_blank">Link</a>&nbsp;';
return result;
}
} else if (${tableid}Columns[co].data === "id" && ${tableid}Columns[co].action === true) {
${tableid}Columns[co].render = function (id) {
let result = '<a href="${url}/' + id + '/view"><i class="fas fa-eye" aria-hidden="true"></i></a>&nbsp;';
if (${allow_edit}) {
result += '<a href="${url}/' + id + '/edit"><i class="fas fa-edit" aria-hidden="true"></i></a>&nbsp;'
} }
} if (${allow_delete}) {
let language = { result += '<a href="${url}/' + id + '/delete"><i class="fas fa-trash" aria-hidden="true"></i></a>';
"search": "Cari: ", }
"paginate": { return result;
"first": '<span class="glyphicon glyphicon-step-backward"></span> ', }
"last": '<span class="glyphicon glyphicon glyphicon-step-forward"></span> ', ${tableid}Columns[co].width = "30pt";
"previous": '<span class="glyphicon glyphicon-backward"></span> ', ${tableid}Columns[co].orderable = false;
"next": '<span class="glyphicon glyphicon-forward"></span> ', ${tableid}Columns[co].className = "text-center";
}, ${tableid}Columns[co].visible = true;
"lengthMenu": " _MENU_ baris " //columns[1].order = "order_asc";
};
let params = {
dom: '<"row"<"col-md-8"<"toolbar">Bl><"col-md-4"fr>>tip',
processing: true,
serverSide: ${server_side},
stateSave: false,
scrollCollapse: true,
sort: ${sorts},
info: false,
filter: ${filters},
autoWidth: false,
paginate: ${paginates},
paginationType: "full_numbers",
order: [],
lengthMenu: [
[10, 25, 50, 100],
[10, 25, 50, 100]
],
columns: columns,
"language": language,
} }
if (${server_side}==false }
) let ${tableid}Language = {
{ "search": "Cari: ",
params.data = ${data}; "paginate": {
} "first": '<span class="glyphicon glyphicon-step-backward"></span> ',
else "last": '<span class="glyphicon glyphicon glyphicon-step-forward"></span> ',
{ "previous": '<span class="glyphicon glyphicon-backward"></span> ',
params.ajax = o${tableid}Url; "next": '<span class="glyphicon glyphicon-forward"></span> ',
},
"lengthMenu": " _MENU_ baris "
};
let ${tableid}Params = {
dom: '<"row"<"col-md-8"<"toolbar">Bl><"col-md-4"fr>>tip',
processing: true,
serverSide: ${server_side},
stateSave: false,
scrollCollapse: true,
sort: ${sorts},
info: false,
filter: ${filters},
autoWidth: false,
paginate: ${paginates},
paginationType: "full_numbers",
order: [],
lengthMenu: [
[10, 25, 50, 100],
[10, 25, 50, 100]
],
columns: ${tableid}Columns,
"language": ${tableid}Language,
}
if (!${server_side}) {
${tableid}Params.data = ${data};
} else {
${tableid}Params.ajax = o${tableid}Url;
}
o${tableid} = $('#${tableid}').DataTable(${tableid}Params);
let tb = tb_array.join(' ');
$("div.toolbar").html(tb);
$("div.toolbar").attr('style', 'display:block; float: left; margin-bottom:6px; line-height:16px;');
$('#${tableid} tbody').on('click', 'tr', function () {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
} else {
let aData = o${tableid}.row(this).data();
o${tableid}.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
m${tableid}ID = aData.id;
//console.log("m${tableid}ID", m${tableid}ID);
o${tableid}.$('tr.row_selected').removeClass('row_selected');
$(this).addClass('row_selected');
} }
o${tableid} = $('#${tableid}').DataTable(params); });
let tb = tb_array.join(' '); ${structure:btnscripts}
$("div.toolbar").html(tb);
$("div.toolbar").attr('style', 'display:block; float: left; margin-bottom:6px; line-height:16px;');
$('#${tableid} tbody').on('click', 'tr', function () {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
} else {
let aData = o${tableid}.row(this).data();
o${tableid}.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
m${tableid}ID = aData.id;
// console.log(mID);
o${tableid}.$('tr.row_selected').removeClass('row_selected');
$(this).addClass('row_selected');
}
});
${structure:btnscripts}
}); });
</script> </script>
</div> </div>
import decimal
import json
import logging import logging
from datetime import datetime
import colander
from icecream import ic from icecream import ic
from pyramid.httpexceptions import HTTPForbidden from pyramid.httpexceptions import HTTPForbidden
from pyramid.httpexceptions import HTTPNotFound from pyramid.httpexceptions import HTTPNotFound
from pyramid.renderers import null_renderer, render from pyramid.renderers import null_renderer, render, JSON
from pyramid.response import Response from pyramid.response import Response
from pyramid.security import NO_PERMISSION_REQUIRED, remember from pyramid.security import NO_PERMISSION_REQUIRED, remember
from pyramid_rpc.jsonrpc import ( from pyramid_rpc.jsonrpc import (
...@@ -255,6 +259,13 @@ class EndpointPredicate(object): ...@@ -255,6 +259,13 @@ class EndpointPredicate(object):
# return a valid JSON-RPC response. # return a valid JSON-RPC response.
return True return True
def json_rpc():
json_r = JSON()
json_r.add_adapter(datetime.datetime, lambda v, request: v.isoformat())
json_r.add_adapter(datetime.date, lambda v, request: v.isoformat())
json_r.add_adapter(decimal.Decimal, lambda v, request: str(v))
json_r.add_adapter(colander.null, lambda v, request: None)
return json_r
def includeme(config): def includeme(config):
""" Set up standard configurator registrations. Use via: """ Set up standard configurator registrations. Use via:
......
...@@ -33,7 +33,7 @@ requires = [ ...@@ -33,7 +33,7 @@ requires = [
'google-api-python-client', 'google-api-python-client',
'google', 'google',
'pyjwt', 'pyjwt',
'z3c.rml', # 'z3c.rml',
'opensipkd-tools @git+https://git.opensipkd.com/aa.gusti/opensipkd-tools.git', 'opensipkd-tools @git+https://git.opensipkd.com/aa.gusti/opensipkd-tools.git',
] ]
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!