mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
add support for more families in the new integrator
This commit is contained in:
parent
cd837f9b12
commit
9fa7e893d8
14 changed files with 129 additions and 94 deletions
|
|
@ -179,7 +179,6 @@ class CollectFileSequences(pyblish.api.ContextPlugin):
|
||||||
"subset": subset,
|
"subset": subset,
|
||||||
"asset": data.get("asset", api.Session["AVALON_ASSET"]),
|
"asset": data.get("asset", api.Session["AVALON_ASSET"]),
|
||||||
"stagingDir": root,
|
"stagingDir": root,
|
||||||
"files": [list(collection)],
|
|
||||||
"startFrame": start,
|
"startFrame": start,
|
||||||
"endFrame": end,
|
"endFrame": end,
|
||||||
"fps": fps,
|
"fps": fps,
|
||||||
|
|
@ -187,6 +186,14 @@ class CollectFileSequences(pyblish.api.ContextPlugin):
|
||||||
})
|
})
|
||||||
instance.append(collection)
|
instance.append(collection)
|
||||||
|
|
||||||
|
representation = {
|
||||||
|
'name': 'jpg',
|
||||||
|
'ext': '.jpg',
|
||||||
|
'files': [list(collection)],
|
||||||
|
"stagingDir": root,
|
||||||
|
}
|
||||||
|
instance.data["representations"] = [representation]
|
||||||
|
|
||||||
if data.get('user'):
|
if data.get('user'):
|
||||||
context.data["user"] = data['user']
|
context.data["user"] = data['user']
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,13 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin):
|
||||||
sub_proc = subprocess.Popen(subprocess_jpeg)
|
sub_proc = subprocess.Popen(subprocess_jpeg)
|
||||||
sub_proc.wait()
|
sub_proc.wait()
|
||||||
|
|
||||||
if "files" not in instance.data:
|
if "representations" not in instance.data:
|
||||||
instance.data["files"] = list()
|
instance.data["representations"] = []
|
||||||
instance.data["files"].append(jpegFile)
|
|
||||||
|
representation = {
|
||||||
|
'name': 'jpg',
|
||||||
|
'ext': '.jpg',
|
||||||
|
'files': jpegFile,
|
||||||
|
"stagingDir": stagingdir,
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,13 @@ class ExtractQuicktimeEXR(pyblish.api.InstancePlugin):
|
||||||
sub_proc = subprocess.Popen(subprocess_mov)
|
sub_proc = subprocess.Popen(subprocess_mov)
|
||||||
sub_proc.wait()
|
sub_proc.wait()
|
||||||
|
|
||||||
if "files" not in instance.data:
|
if "representations" not in instance.data:
|
||||||
instance.data["files"] = list()
|
instance.data["representations"] = []
|
||||||
instance.data["files"].append(movFile)
|
|
||||||
|
representation = {
|
||||||
|
'name': 'mov',
|
||||||
|
'ext': '.mov',
|
||||||
|
'files': movFile,
|
||||||
|
"stagingDir": stagingdir,
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,10 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
||||||
label = "Integrate Asset"
|
label = "Integrate Asset"
|
||||||
order = pyblish.api.IntegratorOrder
|
order = pyblish.api.IntegratorOrder
|
||||||
families = ["look",
|
families = ["look",
|
||||||
"vdbcache",
|
|
||||||
"assembly",
|
"assembly",
|
||||||
"vrayproxy",
|
|
||||||
"yetiRig",
|
"yetiRig",
|
||||||
"yeticache",
|
"yeticache",
|
||||||
"nukescript",
|
"nukescript",
|
||||||
"review",
|
|
||||||
"scene",
|
|
||||||
"render",
|
|
||||||
"imagesequence",
|
|
||||||
"write"]
|
"write"]
|
||||||
exclude_families = ["clip"]
|
exclude_families = ["clip"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
||||||
'anatomy_template': 'publish' or 'render', etc.
|
'anatomy_template': 'publish' or 'render', etc.
|
||||||
template from anatomy that should be used for
|
template from anatomy that should be used for
|
||||||
integrating this file. Only the first level can
|
integrating this file. Only the first level can
|
||||||
be specified right now.
|
be specified right now.
|
||||||
'startFrame'
|
'startFrame'
|
||||||
'endFrame'
|
'endFrame'
|
||||||
'framerate'
|
'framerate'
|
||||||
|
|
@ -50,7 +50,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
||||||
"mayaAscii",
|
"mayaAscii",
|
||||||
"setdress",
|
"setdress",
|
||||||
"layout",
|
"layout",
|
||||||
"ass"
|
"ass",
|
||||||
|
"vdbcache",
|
||||||
|
"scene",
|
||||||
|
"vrayproxy",
|
||||||
|
"render",
|
||||||
|
"imagesequence",
|
||||||
|
"review"
|
||||||
]
|
]
|
||||||
exclude_families = ["clip"]
|
exclude_families = ["clip"]
|
||||||
|
|
||||||
|
|
@ -281,7 +287,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
||||||
dst = anatomy_filled[template_name]["path"]
|
dst = anatomy_filled[template_name]["path"]
|
||||||
|
|
||||||
instance.data["transfers"].append([src, dst])
|
instance.data["transfers"].append([src, dst])
|
||||||
# template = anatomy.templates["publish"]["path"]
|
|
||||||
instance.data["representations"][idx]['published_path'] = dst
|
instance.data["representations"][idx]['published_path'] = dst
|
||||||
|
|
||||||
representation = {
|
representation = {
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,15 @@ class ExtractVDBCache(pype.api.Extractor):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise RuntimeError("Render failed: {0}".format(exc))
|
raise RuntimeError("Render failed: {0}".format(exc))
|
||||||
|
|
||||||
if "files" not in instance.data:
|
|
||||||
instance.data["files"] = []
|
|
||||||
|
|
||||||
output = instance.data["frames"]
|
output = instance.data["frames"]
|
||||||
|
|
||||||
instance.data["files"].append(output)
|
if "representations" not in instance.data:
|
||||||
|
instance.data["representations"] = []
|
||||||
|
|
||||||
|
representation = {
|
||||||
|
'name': 'mov',
|
||||||
|
'ext': '.mov',
|
||||||
|
'files': output,
|
||||||
|
"stagingDir": staging_dir,
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
|
||||||
|
|
@ -146,9 +146,9 @@ class ExtractFBX(pype.api.Extractor):
|
||||||
cmds.loadPlugin("fbxmaya", quiet=True)
|
cmds.loadPlugin("fbxmaya", quiet=True)
|
||||||
|
|
||||||
# Define output path
|
# Define output path
|
||||||
directory = self.staging_dir(instance)
|
stagingDir = self.staging_dir(instance)
|
||||||
filename = "{0}.fbx".format(instance.name)
|
filename = "{0}.fbx".format(instance.name)
|
||||||
path = os.path.join(directory, filename)
|
path = os.path.join(stagingDir, filename)
|
||||||
|
|
||||||
# The export requires forward slashes because we need
|
# The export requires forward slashes because we need
|
||||||
# to format it into a string in a mel expression
|
# to format it into a string in a mel expression
|
||||||
|
|
@ -208,9 +208,16 @@ class ExtractFBX(pype.api.Extractor):
|
||||||
cmds.select(members, r=1, noExpand=True)
|
cmds.select(members, r=1, noExpand=True)
|
||||||
mel.eval('FBXExport -f "{}" -s'.format(path))
|
mel.eval('FBXExport -f "{}" -s'.format(path))
|
||||||
|
|
||||||
if "files" not in instance.data:
|
if "representations" not in instance.data:
|
||||||
instance.data["files"] = list()
|
instance.data["representations"] = []
|
||||||
|
|
||||||
|
representation = {
|
||||||
|
'name': 'mov',
|
||||||
|
'ext': '.mov',
|
||||||
|
'files': filename,
|
||||||
|
"stagingDir": stagingDir,
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
||||||
instance.data["files"].append(filename)
|
|
||||||
|
|
||||||
self.log.info("Extract FBX successful to: {0}".format(path))
|
self.log.info("Extract FBX successful to: {0}".format(path))
|
||||||
|
|
|
||||||
|
|
@ -110,9 +110,9 @@ class ExtractThumbnail(pype.api.Extractor):
|
||||||
"depthOfField": cmds.getAttr("{0}.depthOfField".format(camera)),
|
"depthOfField": cmds.getAttr("{0}.depthOfField".format(camera)),
|
||||||
}
|
}
|
||||||
|
|
||||||
stagingdir = self.staging_dir(instance)
|
stagingDir = self.staging_dir(instance)
|
||||||
filename = "{0}".format(instance.name)
|
filename = "{0}".format(instance.name)
|
||||||
path = os.path.join(stagingdir, filename)
|
path = os.path.join(stagingDir, filename)
|
||||||
|
|
||||||
self.log.info("Outputting images to %s" % path)
|
self.log.info("Outputting images to %s" % path)
|
||||||
|
|
||||||
|
|
@ -131,51 +131,19 @@ class ExtractThumbnail(pype.api.Extractor):
|
||||||
_, thumbnail = os.path.split(playblast)
|
_, thumbnail = os.path.split(playblast)
|
||||||
|
|
||||||
self.log.info("file list {}".format(thumbnail))
|
self.log.info("file list {}".format(thumbnail))
|
||||||
# self.log.info("Calculating HUD data overlay")
|
|
||||||
|
|
||||||
# stagingdir = "C:/Users/milan.kolar/AppData/Local/Temp/pyblish_tmp_ucsymm"
|
if "representations" not in instance.data:
|
||||||
# collected_frames = os.listdir(stagingdir)
|
instance.data["representations"] = []
|
||||||
# collections, remainder = clique.assemble(collected_frames)
|
|
||||||
# input_path = os.path.join(stagingdir, collections[0].format('{head}{padding}{tail}'))
|
|
||||||
# self.log.info("input {}".format(input_path))
|
|
||||||
|
|
||||||
# movieFile = filename + ".mov"
|
representation = {
|
||||||
# full_movie_path = os.path.join(stagingdir, movieFile)
|
'name': 'thumbnail',
|
||||||
# self.log.info("output {}".format(full_movie_path))
|
'ext': '.jpg',
|
||||||
# fls = [os.path.join(stagingdir, filename).replace("\\","/") for f in os.listdir( dir_path ) if f.endswith(preset['compression'])]
|
'files': thumbnail,
|
||||||
# self.log.info("file list {}}".format(fls[0]))
|
"stagingDir": stagingDir,
|
||||||
|
"thumbnail": True
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
||||||
# out, err = (
|
|
||||||
# ffmpeg
|
|
||||||
# .input(input_path, framerate=25)
|
|
||||||
# .output(full_movie_path)
|
|
||||||
# .run(overwrite_output=True)
|
|
||||||
# )
|
|
||||||
|
|
||||||
if "files" not in instance.data:
|
|
||||||
instance.data["files"] = list()
|
|
||||||
instance.data["files"].append(thumbnail)
|
|
||||||
|
|
||||||
# ftrackStrings = fStrings.annotationData()
|
|
||||||
# nData = ftrackStrings.niceData
|
|
||||||
# nData['version'] = instance.context.data('version')
|
|
||||||
# fFrame = int(pm.playbackOptions( q = True, minTime = True))
|
|
||||||
# eFrame = int(pm.playbackOptions( q = True, maxTime = True))
|
|
||||||
# nData['frame'] = [(str("{0:05d}".format(f))) for f in range(fFrame, eFrame + 1)]
|
|
||||||
# soundOfst = int(float(nData['oFStart'])) - int(float(nData['handle'])) - fFrame
|
|
||||||
# soundFile = mu.giveMePublishedAudio()
|
|
||||||
# self.log.info("SOUND offset %s" % str(soundOfst))
|
|
||||||
# self.log.info("SOUND source video to %s" % str(soundFile))
|
|
||||||
# ann = dHUD.draftAnnotate()
|
|
||||||
# if soundFile:
|
|
||||||
# ann.addAnotation(seqFls = fls, outputMoviePth = movieFullPth, annotateDataArr = nData, soundFile = soundFile, soundOffset = soundOfst)
|
|
||||||
# else:
|
|
||||||
# ann.addAnotation(seqFls = fls, outputMoviePth = movieFullPth, annotateDataArr = nData)
|
|
||||||
|
|
||||||
# for f in fls:
|
|
||||||
# os.remove(f)
|
|
||||||
|
|
||||||
# playblast = (ann.expPth).replace("\\","/")
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,16 @@ class ExtractVRayProxy(pype.api.Extractor):
|
||||||
ignoreHiddenObjects=True,
|
ignoreHiddenObjects=True,
|
||||||
createProxyNode=False)
|
createProxyNode=False)
|
||||||
|
|
||||||
if "files" not in instance.data:
|
if "representations" not in instance.data:
|
||||||
instance.data["files"] = list()
|
instance.data["representations"] = []
|
||||||
|
|
||||||
instance.data["files"].append(file_name)
|
representation = {
|
||||||
|
'name': 'vrmesh',
|
||||||
|
'ext': '.vrmesh',
|
||||||
|
'files': file_name,
|
||||||
|
"stagingDir": staging_dir,
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
||||||
self.log.info("Extracted instance '%s' to: %s"
|
self.log.info("Extracted instance '%s' to: %s"
|
||||||
% (instance.name, staging_dir))
|
% (instance.name, staging_dir))
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,16 @@ class CollectNukeReads(pyblish.api.ContextPlugin):
|
||||||
|
|
||||||
self.log.debug("collected_frames: {}".format(label))
|
self.log.debug("collected_frames: {}".format(label))
|
||||||
|
|
||||||
instance.data["files"] = [source_files]
|
if "representations" not in instance.data:
|
||||||
|
instance.data["representations"] = []
|
||||||
|
|
||||||
|
representation = {
|
||||||
|
'name': ext,
|
||||||
|
'ext': "." + ext,
|
||||||
|
'files': source_files,
|
||||||
|
"stagingDir": source_dir,
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
||||||
transfer = False
|
transfer = False
|
||||||
if "publish" in node.knobs():
|
if "publish" in node.knobs():
|
||||||
|
|
|
||||||
|
|
@ -63,12 +63,16 @@ class CollectNukeWrites(pyblish.api.ContextPlugin):
|
||||||
int(last_frame)
|
int(last_frame)
|
||||||
)
|
)
|
||||||
|
|
||||||
if "files" not in instance.data:
|
if "representations" not in instance.data:
|
||||||
instance.data["files"] = list()
|
instance.data["representations"] = []
|
||||||
try:
|
|
||||||
collected_frames = os.listdir(output_dir)
|
representation = {
|
||||||
self.log.debug("collected_frames: {}".format(label))
|
'name': ext,
|
||||||
instance.data["files"].append(collected_frames)
|
'ext': "." + ext,
|
||||||
|
'files': collected_frames,
|
||||||
|
"stagingDir": output_dir,
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.debug("couldn't collect frames: {}".format(label))
|
self.log.debug("couldn't collect frames: {}".format(label))
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class ExtractDataForReview(pype.api.Extractor):
|
||||||
# Deselect all nodes to prevent external connections
|
# Deselect all nodes to prevent external connections
|
||||||
[i["selected"].setValue(False) for i in nuke.allNodes()]
|
[i["selected"].setValue(False) for i in nuke.allNodes()]
|
||||||
self.log.debug("creating staging dir:")
|
self.log.debug("creating staging dir:")
|
||||||
self.staging_dir(instance)
|
self.stagingDir(instance)
|
||||||
|
|
||||||
self.log.debug("instance: {}".format(instance))
|
self.log.debug("instance: {}".format(instance))
|
||||||
self.log.debug("instance.data[families]: {}".format(
|
self.log.debug("instance.data[families]: {}".format(
|
||||||
|
|
@ -50,10 +50,10 @@ class ExtractDataForReview(pype.api.Extractor):
|
||||||
|
|
||||||
def transcode_mov(self, instance):
|
def transcode_mov(self, instance):
|
||||||
collection = instance.data["collection"]
|
collection = instance.data["collection"]
|
||||||
staging_dir = instance.data["stagingDir"].replace("\\", "/")
|
stagingDir = instance.data["stagingDir"].replace("\\", "/")
|
||||||
file_name = collection.format("{head}mov")
|
file_name = collection.format("{head}mov")
|
||||||
|
|
||||||
review_mov = os.path.join(staging_dir, file_name).replace("\\", "/")
|
review_mov = os.path.join(stagingDir, file_name).replace("\\", "/")
|
||||||
|
|
||||||
self.log.info("transcoding review mov: {0}".format(review_mov))
|
self.log.info("transcoding review mov: {0}".format(review_mov))
|
||||||
if instance.data.get("baked_colorspace_movie"):
|
if instance.data.get("baked_colorspace_movie"):
|
||||||
|
|
@ -85,8 +85,8 @@ class ExtractDataForReview(pype.api.Extractor):
|
||||||
|
|
||||||
import nuke
|
import nuke
|
||||||
temporary_nodes = []
|
temporary_nodes = []
|
||||||
staging_dir = instance.data["stagingDir"].replace("\\", "/")
|
stagingDir = instance.data["stagingDir"].replace("\\", "/")
|
||||||
self.log.debug("StagingDir `{0}`...".format(staging_dir))
|
self.log.debug("StagingDir `{0}`...".format(stagingDir))
|
||||||
|
|
||||||
collection = instance.data.get("collection", None)
|
collection = instance.data.get("collection", None)
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ class ExtractDataForReview(pype.api.Extractor):
|
||||||
node = previous_node = nuke.createNode("Read")
|
node = previous_node = nuke.createNode("Read")
|
||||||
|
|
||||||
node["file"].setValue(
|
node["file"].setValue(
|
||||||
os.path.join(staging_dir, fname).replace("\\", "/"))
|
os.path.join(stagingDir, fname).replace("\\", "/"))
|
||||||
|
|
||||||
node["first"].setValue(first_frame)
|
node["first"].setValue(first_frame)
|
||||||
node["origfirst"].setValue(first_frame)
|
node["origfirst"].setValue(first_frame)
|
||||||
|
|
@ -147,7 +147,7 @@ class ExtractDataForReview(pype.api.Extractor):
|
||||||
|
|
||||||
if representation in "mov":
|
if representation in "mov":
|
||||||
file = fhead + "baked.mov"
|
file = fhead + "baked.mov"
|
||||||
path = os.path.join(staging_dir, file).replace("\\", "/")
|
path = os.path.join(stagingDir, file).replace("\\", "/")
|
||||||
self.log.debug("Path: {}".format(path))
|
self.log.debug("Path: {}".format(path))
|
||||||
instance.data["baked_colorspace_movie"] = path
|
instance.data["baked_colorspace_movie"] = path
|
||||||
write_node["file"].setValue(path)
|
write_node["file"].setValue(path)
|
||||||
|
|
@ -158,7 +158,7 @@ class ExtractDataForReview(pype.api.Extractor):
|
||||||
|
|
||||||
elif representation in "jpeg":
|
elif representation in "jpeg":
|
||||||
file = fhead + "jpeg"
|
file = fhead + "jpeg"
|
||||||
path = os.path.join(staging_dir, file).replace("\\", "/")
|
path = os.path.join(stagingDir, file).replace("\\", "/")
|
||||||
instance.data["thumbnail"] = path
|
instance.data["thumbnail"] = path
|
||||||
write_node["file"].setValue(path)
|
write_node["file"].setValue(path)
|
||||||
write_node["file_type"].setValue("jpeg")
|
write_node["file_type"].setValue("jpeg")
|
||||||
|
|
@ -170,7 +170,17 @@ class ExtractDataForReview(pype.api.Extractor):
|
||||||
first_frame = int(last_frame) / 2
|
first_frame = int(last_frame) / 2
|
||||||
last_frame = int(last_frame) / 2
|
last_frame = int(last_frame) / 2
|
||||||
# add into files for integration as representation
|
# add into files for integration as representation
|
||||||
instance.data["files"].append(file)
|
|
||||||
|
if "representations" not in instance.data:
|
||||||
|
instance.data["representations"] = []
|
||||||
|
|
||||||
|
representation = {
|
||||||
|
'name': 'nk',
|
||||||
|
'ext': '.nk',
|
||||||
|
'files': file,
|
||||||
|
"stagingDir": stagingdir,
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
||||||
# Render frames
|
# Render frames
|
||||||
nuke.execute(write_node.name(), int(first_frame), int(last_frame))
|
nuke.execute(write_node.name(), int(first_frame), int(last_frame))
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,22 @@ class ExtractScript(pype.api.Extractor):
|
||||||
current_script = instance.context.data["currentFile"]
|
current_script = instance.context.data["currentFile"]
|
||||||
|
|
||||||
# Define extract output file path
|
# Define extract output file path
|
||||||
dir_path = self.staging_dir(instance)
|
stagingdir = self.staging_dir(instance)
|
||||||
filename = "{0}".format(instance.data["name"])
|
filename = "{0}".format(instance.data["name"])
|
||||||
path = os.path.join(dir_path, filename)
|
path = os.path.join(stagingdir, filename)
|
||||||
|
|
||||||
self.log.info("Performing extraction..")
|
self.log.info("Performing extraction..")
|
||||||
shutil.copy(current_script, path)
|
shutil.copy(current_script, path)
|
||||||
|
|
||||||
if "files" not in instance.data:
|
if "representations" not in instance.data:
|
||||||
instance.data["files"] = list()
|
instance.data["representations"] = []
|
||||||
|
|
||||||
instance.data["files"].append(filename)
|
representation = {
|
||||||
|
'name': 'nk',
|
||||||
|
'ext': '.nk',
|
||||||
|
'files': filename,
|
||||||
|
"stagingDir": stagingdir,
|
||||||
|
}
|
||||||
|
instance.data["representations"].append(representation)
|
||||||
|
|
||||||
self.log.info("Extracted instance '%s' to: %s" % (instance.name, path))
|
self.log.info("Extracted instance '%s' to: %s" % (instance.name, path))
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin):
|
||||||
self.log.info("collected instance: {}".format(instance.data))
|
self.log.info("collected instance: {}".format(instance.data))
|
||||||
self.log.info("parsing data: {}".format(in_data))
|
self.log.info("parsing data: {}".format(in_data))
|
||||||
|
|
||||||
# instance.data["files"] = list()
|
|
||||||
instance.data['destination_list'] = list()
|
instance.data['destination_list'] = list()
|
||||||
instance.data['representations'] = list()
|
instance.data['representations'] = list()
|
||||||
instance.data['source'] = 'standalone publisher'
|
instance.data['source'] = 'standalone publisher'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue