Commit b1fc3c9d by Bodor Máté

Merge DEV to image_model_test

parents 44c637b3 3a808815
# IDEs
.vscode/
recircle/db.sqlite3
recircle/clouds.yaml
environment.sh
.idea/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
example.py
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don’t work, or not
# install all needed dependencies.
#Pipfile.lock
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
environment.sh
# Cloud configure
clouds.yaml
......@@ -13,11 +13,11 @@ django-nose = "*"
[packages]
django = "*"
djangorestframework = "*"
djoser = "*"
django-cors-headers = "*"
openstacksdk = "*"
python-novaclient = "*"
keystoneauth1 = "*"
djoser = "*"
[requires]
python_version = "3.6"
......@@ -121,11 +121,11 @@
},
"django": {
"hashes": [
"sha256:4d23f61b26892bac785f07401bc38cbf8fa4cec993f400e9cd9ddf28fd51c0ea",
"sha256:6e974d4b57e3b29e4882b244d40171d6a75202ab8d2402b8e8adbd182e25cf0c"
"sha256:16a5d54411599780ac9dfe3b9b38f90f785c51259a584e0b24b6f14a7f69aae8",
"sha256:9a2f98211ab474c710fcdad29c82f30fc14ce9917c7a70c3682162a624de4035"
],
"index": "pypi",
"version": "==2.2.3"
"version": "==2.2.4"
},
"django-cors-headers": {
"hashes": [
......@@ -272,11 +272,11 @@
},
"openstacksdk": {
"hashes": [
"sha256:dc1232a58e30876ed6b28ee7bf5fee281b70048b187e88acd8a53cc3b63f9d8a",
"sha256:f6e045d2e0a111d3a8dd157d111a04f1354ad6cc7819f6c1db073426bea7734f"
"sha256:ae521b4083ecc3395e27b6a7d0f119a737cefb2f76277f16dc5b626a3c4d5c52",
"sha256:e3a1346a238d57d4f398f345a79d03b6705e229a4453f8b73acdcd00d04e6328"
],
"index": "pypi",
"version": "==0.31.2"
"version": "==0.32.0"
},
"os-service-types": {
"hashes": [
......@@ -308,10 +308,10 @@
},
"pbr": {
"hashes": [
"sha256:0ca44dc9fd3b04a22297c2a91082d8df2894862e8f4c86a49dac69eae9e85ca0",
"sha256:4aed6c1b1fa5020def0f22aed663d87b81bb3235f112490b07d2643d7a98c5b5"
"sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc",
"sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf"
],
"version": "==5.4.1"
"version": "==5.4.2"
},
"prettytable": {
"hashes": [
......@@ -344,26 +344,28 @@
},
"pytz": {
"hashes": [
"sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda",
"sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"
"sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32",
"sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"
],
"version": "==2019.1"
"version": "==2019.2"
},
"pyyaml": {
"hashes": [
"sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3",
"sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043",
"sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7",
"sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265",
"sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391",
"sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778",
"sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225",
"sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955",
"sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e",
"sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190",
"sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd"
],
"version": "==5.1.1"
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
],
"version": "==5.1.2"
},
"requests": {
"hashes": [
......
......@@ -86,7 +86,9 @@ class Image(models.Model):
uploaded_by_user=False,
description=description
)
return new_image
for attr, value in new_image.__dict__.items():
setattr(remote_image, attr, value)
return remote_image
@classmethod
def create_from_user(cls, description,
......@@ -102,4 +104,24 @@ class Image(models.Model):
uploaded_by_user=True,
description=description
)
return new_image
for attr, value in new_image.__dict__.items():
setattr(remote_image, attr, value)
return remote_image
def delete(self):
interface = OpenstackImageManager(settings.CONNECTION)
if interface.delete(self.remote_id):
super().delete()
def get(self):
interface = OpenstackImageManager(settings.CONNECTION)
image = interface.get(self.remote_id)
for attr, value in self.__dict__.items():
setattr(image, attr, value)
return image
def update(self, data):
for (key, value) in data.items():
setattr(self, key, value)
self.save()
return self.get()
......@@ -3,9 +3,15 @@ from rest_framework import serializers
from .models import Image
class ImageUpdateSerializer(serializers.Serializer):
name = serializers.CharField(required=False)
description = serializers.CharField(required=False)
class ImageSerializer(serializers.ModelSerializer):
image_file = serializers.FileField(write_only=True)
file_format = serializers.CharField(max_length=10, write_only=True)
file_format = serializers.CharField(max_length=10)
size = serializers.IntegerField()
class Meta:
model = Image
......@@ -18,5 +24,7 @@ class ImageSerializer(serializers.ModelSerializer):
"created_at",
"uploaded_by_user",
"created_by",
"id",
"size",
)
read_only_fields = ("created_at", "uploaded_by_user", "created_by", "remote_id", )
read_only_fields = ("created_at", "uploaded_by_user", "created_by", "remote_id", "size", )
# from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from rest_framework import status
from django.core.exceptions import ObjectDoesNotExist
from image.models import Image
from image.serializers import ImageSerializer
from image.serializers import ImageUpdateSerializer
class ImageViewSet(ModelViewSet):
serializer_class = ImageSerializer
queryset = Image.objects.all()
# def list(self, request):
# return HttpResponse("list")
class ImageViewSet(ViewSet):
def list(self, request):
image_list = []
images = Image.objects.all()
for image in images:
image_list.append(image.get())
serializer = ImageSerializer(image_list, many=True)
return Response(serializer.data)
def create(self, request):
serializer = ImageSerializer(data=request.data)
......@@ -26,14 +34,30 @@ class ImageViewSet(ModelViewSet):
serializer = ImageSerializer(instance=new_image)
return Response(serializer.data)
# def retrieve(self, request, pk=None):
# return HttpResponse("retrive")
def retrieve(self, request, pk=None):
queryset = Image.objects.all()
image = get_object_or_404(queryset, pk=pk)
serializer = ImageSerializer(instance=image)
image = image.get()
serializer = ImageSerializer(instance=image)
return Response(serializer.data)
# def update(self, request, pk=None):
# return HttpResponse("update")
def update(self, request, pk=None):
serializer = ImageUpdateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
queryset = Image.objects.all()
image = get_object_or_404(queryset, pk=pk)
image = image.update(serializer.validated_data)
serializer = ImageSerializer(instance=image)
return Response(serializer.data)
# def partial_update(self, request, pk=None):
# return HttpResponse("patch")
def partial_update(self, request, pk=None):
return self.update(request, pk)
# def destroy(self, request, pk=None):
# return HttpResponse("delete")
def destroy(self, request, pk=None):
try:
image = Image.objects.get(id=pk)
image.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except ObjectDoesNotExist:
return Response(status=status.HTTP_204_NO_CONTENT)
# Generated by Django 2.2.3 on 2019-07-15 09:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instance', '0008_auto_20190704_1310'),
]
operations = [
migrations.AlterField(
model_name='instance',
name='flavor',
field=models.ForeignKey(help_text='Reasources given to the vm', on_delete='CASCADE', related_name='instances', to='instance.Flavor', verbose_name='flavor'),
),
migrations.AlterField(
model_name='instance',
name='lease',
field=models.ForeignKey(on_delete='CASCADE', related_name='instances', to='instance.Lease'),
),
]
# Generated by Django 2.2.3 on 2019-07-31 12:15
# Generated by Django 2.2.4 on 2019-08-08 10:41
from django.db import migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instance', '0009_auto_20190715_0929'),
('template', '0007_basetemplate_network_id'),
('instance', '0009_auto_20190715_1354'),
]
operations = [
migrations.AddField(
model_name='instance',
name='template',
field=models.ForeignKey(help_text='The base image of the vm', null=True, on_delete='DO_NOTHING', related_name='vm', to='template.ImageTemplate'),
),
]
......@@ -15,6 +15,17 @@ ACCESS_METHODS = tuple(
[(key, val[0]) for key, val in settings.VM_ACCESS_PROTOCOLS.items()]
)
interface = OSVirtualMachineManager(settings.CONNECTION)
ACTIONS = {
"start": interface.start_vm,
"stop": interface.stop_vm,
"suspend": interface.suspend_vm,
"wake_up": interface.wake_up_vm,
"reset": interface.reset_vm,
"reboot": interface.reboot_vm,
}
class Lease(models.Model):
""" Users can use the virtual machine until the lease dates.
......@@ -44,7 +55,6 @@ class Flavor(models.Model):
@classmethod
def create(cls, name, description, ram=0, vcpu=0,
initial_disk=0, priority=0):
interface = OSVirtualMachineManager(settings.CONNECTION)
try:
remote_flavor = interface.create_flavor(name, ram, vcpu, initial_disk)
......@@ -55,12 +65,22 @@ class Flavor(models.Model):
return flavor
except Exception as e:
logger.error(str(e))
raise ValueError("Couldn't create Flavor in remote cloud.")
raise ValueError("Can't create Flavor in remote cloud.")
def delete(self):
interface = OSVirtualMachineManager(settings.CONNECTION)
try:
interface.delete_flavor(self.remote_id)
super(Flavor, self).delete()
except Exception as e:
if e.OpenStackError:
logger.error("Can not delete the flavor in remote cloud")
class Instance(models.Model):
"""Virtual machine instance.
"""
from template.models import ImageTemplate
name = models.CharField(max_length=100,
help_text="Human readable name of instance")
......@@ -92,11 +112,9 @@ class Instance(models.Model):
default=False,
)
# image = models.ForeignKey(TemplateImage, related_name="vm", null=True,
# help_text="The base image of the vm")
#
# snapshot = models.ForeignKey(Snapshot, related_name="vm", null=True,
# help_text="The base snapshot of the vm")
template = models.ForeignKey(ImageTemplate, related_name="vm", null=True,
help_text="The base image of the vm",
on_delete="DO_NOTHING")
disks = models.ManyToManyField(Disk, related_name="instance",
help_text="Disks attached to instance",
......@@ -110,9 +128,11 @@ class Instance(models.Model):
related_name='instances')
@classmethod
def create(cls, lease, owner, flavor, remote_id, params):
def create(cls, lease, owner, flavor, template, remote_id, params):
params["password"] = cls.generate_password()
inst = cls(lease=lease, flavor=flavor, owner=owner,
remote_id=remote_id, **params)
remote_id=remote_id, template=template, **params)
inst.full_clean()
inst.save()
......@@ -123,19 +143,18 @@ class Instance(models.Model):
def create_instance_from_template(cls, params, template, owner, lease,
disks, networks, flavor):
# TODO: attach disks when the remote instance created
interface = OSVirtualMachineManager(settings.CONNECTION)
try:
remote_inst = interface.create_vm_from_template(params["name"],
template.remote_ID,
remote_id = interface.create_vm_from_template(params["name"],
template.image.remote_id,
flavor.remote_id,
networks,
)
remote_id = remote_inst.id
new_inst = cls.create(lease, owner, flavor, remote_id, params)
new_inst = cls.create(lease, owner, flavor, template,
remote_id, params)
return new_inst
except Exception as e:
logger.error(str(e))
raise ValueError("Couldn't create Flavor in remote cloud."
raise ValueError("Can't create Instance in remote cloud."
"Search the logs for more detail.")
def clean(self, *args, **kwargs):
......@@ -153,3 +172,26 @@ class Instance(models.Model):
timezone.now() + timedelta(
seconds=lease.delete_interval_in_sec)
)
def delete(self):
try:
interface.destroy_vm(self.remote_id)
super(Instance, self).delete()
except Exception as e:
if e.OpenStackError:
logger.error("Can not delete the instance in remote cloud")
def execute_common_action(self, action):
if ACTIONS[action]:
return ACTIONS[action](self.remote_id)
else:
raise ValueError("This action is not supported!")
def get_remote_instance(self):
return interface.get_vm(self.remote_id)
@classmethod
def generate_password(self):
return User.objects.make_random_password(
allowed_chars='abcdefghijklmnopqrstuvwx'
'ABCDEFGHIJKLMNOPQRSTUVWX123456789')
from rest_framework import serializers
from .models import Flavor, Instance
from .models import Flavor, Instance, Lease
class InstanceSerializer(serializers.ModelSerializer):
......@@ -8,10 +8,26 @@ class InstanceSerializer(serializers.ModelSerializer):
class Meta:
model = Instance
fields = ("name", "description", "system", "lease", "flavor")
fields = (
"name",
"description",
"system",
"lease",
"flavor",
"password",
"template",
"time_of_suspend",
"time_of_delete")
read_only_fields = ("password", "template", "time_of_suspend", "time_of_delete")
class FlavorSerializer(serializers.ModelSerializer):
class Meta:
model = Flavor
fields = "__all__"
class LeaseSerializer(serializers.ModelSerializer):
class Meta:
model = Lease
fields = '__all__'
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from instance import views
from rest_framework import routers
urlpatterns = [
path("instances/", views.InstanceList.as_view()),
path("instances/<int:pk>/", views.InstanceDetail.as_view()),
# path('instances/<int:pk>/action/', views.InstanceAction.as_view())
path("flavors/", views.FlavorListView.as_view()),
]
router = routers.SimpleRouter()
router.register(r'instances', views.InstanceViewSet, basename='instance')
router.register(r'flavors', views.FlavorViewSet, basename='flavor')
router.register(r'leases', views.LeaseViewSet, basename='lease')
urlpatterns = format_suffix_patterns(urlpatterns)
urlpatterns = router.urls
from instance.serializers import InstanceSerializer, FlavorSerializer, LeaseSerializer
from django.http import Http404
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet, ModelViewSet
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import action
from interface_openstack.implementation.vm.instance import (
OSVirtualMachineManager
)
from template.serializers import InstanceFromTemplateSerializer
from instance.models import Instance, Flavor, Lease
from instance.serializers import InstanceSerializer, FlavorSerializer
from template.models import ImageTemplate
from template.serializers import ImageTemplateModelSerializer
class InstanceViewSet(ViewSet):
def get_object(self, pk):
try:
return Instance.objects.get(pk=pk)
except Instance.DoesNotExist:
raise Http404
class InstanceList(APIView):
def get(self, request, format=None):
def list(self, request):
instances = Instance.objects.all()
return Response(InstanceSerializer(instances, many=True).data)
def post(self, request, format=None):
def create(self, request):
data = request.data
template = ImageTemplate.objects.get(pk=data["template"])
flavor = Flavor.objects.get(pk=data["flavor"])
lease = Lease.objects.get(pk=data["lease"])
# TODO: if the user can select anouther lease and flavor
# That will be applied, not from the template
# flavor = Flavor.objects.get(pk=data["flavor"])
# lease = Lease.objects.get(pk=data["lease"])
newInstance = Instance.create_instance_from_template(
params={"name": data["name"],
......@@ -29,40 +39,26 @@ class InstanceList(APIView):
"access_method": data["access"],
"system": data["system"],
},
lease=lease,
networks=template.networks,
lease=template.lease,
networks=[{"uuid": template.network_id}],
template=template,
flavor=flavor,
flavor=template.flavor,
owner=request.user,
disks=template.disks
disks=None
)
return Response(newInstance.pk)
class InstanceDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Instance.objects.get(pk=pk)
except Instance.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
def retrieve(self, request, pk):
instance = self.get_object(pk)
instanceDict = InstanceSerializer(instance).data
interface = OSVirtualMachineManager(settings.CONNECTION)
remoteInstance = interface.get_vm(instance.remote_id)
remoteInstance = instance.get_remote_instance()
remoteInstanceDict = remoteInstance.__dict__
merged_dict = {**instanceDict, **remoteInstanceDict}
return Response(merged_dict)
def put(self, request, pk, format=None):
def update(self, request, pk, format=None):
instance = self.get_object(pk)
serializer = InstanceSerializer(instance, data=request.data)
if serializer.is_valid():
......@@ -70,22 +66,48 @@ class InstanceDetail(APIView):
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
def destroy(self, request, pk, format=None):
instance = self.get_object(pk)
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["post"])
def template(self, request, pk):
instance = self.get_object(pk)
serializer = InstanceFromTemplateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
new_template = ImageTemplate.create_from_instance(data["name"],
data["description"],
instance,
request.user)
serializer = ImageTemplateModelSerializer(instance=new_template)
return Response(serializer.data)
class FlavorListView(APIView):
@action(detail=True, methods=["POST"])
def actions(self, request, pk):
instance = self.get_object(pk)
success = instance.execute_common_action(action=request.data["action"])
return Response(success)
class FlavorViewSet(ViewSet):
"""
Create, update or delete a flavor.
"""
def get(self, request, format=None):
def get_object(self, pk):
try:
return Flavor.objects.get(pk=pk)
except Flavor.DoesNotExist:
raise Http404
def list(self, request, format=None):
flavors = Flavor.objects.all()
return Response(FlavorSerializer(flavors, many=True).data)
def post(self, request, format=None):
def create(self, request, format=None):
data = request.data
new_flavor = Flavor.create(name=data["name"],
description=data["description"],
......@@ -95,3 +117,19 @@ class FlavorListView(APIView):
priority=data["priority"])
return Response(new_flavor.pk)
def update(self, request, pk):
return Response(status=status.HTTP_400_BAD_REQUEST)
def partial_update(self, request, pk):
return Response(status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, pk):
flavor = self.get_object(pk)
flavor.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class LeaseViewSet(ModelViewSet):
queryset = Lease.objects.all()
serializer_class = LeaseSerializer
Subproject commit e01d873c78ac17fed0438936f979de3cbaca6a5e
Subproject commit 1a19e4355f4af1abb49a3f6e07dc3a6c3f8bdf47
# Generated by Django 2.2.3 on 2019-08-07 12:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('template', '0006_auto_20190719_1416'),
]
operations = [
migrations.AddField(
model_name='basetemplate',
name='network_id',
field=models.CharField(blank=True, help_text='The new instance will be in this network.', max_length=100, null=True, verbose_name='network_id'),
),
]
......@@ -2,10 +2,8 @@ from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from image.models import Disk
from image.models import Image
from instance.models import Lease
from instance.models import Flavor
from image.models import Disk, Image
from instance.models import Lease, Flavor
from interface_openstack.implementation.storage.openstack_snapshot_manager import SnapshotManager
......@@ -46,6 +44,13 @@ class BaseTemplate(models.Model):
related_name='templates')
lease = models.ForeignKey(Lease, on_delete="CASCADE",
related_name='templates')
network_id = models.CharField(
max_length=100,
verbose_name="network_id",
help_text="The new instance will be in this network.",
null=True,
blank=True
)
class DiskTemplate(BaseTemplate):
......
# from django.shortcuts import render
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
from template.serializers import InstanceFromTemplateSerializer
from template.serializers import ImageTemplateModelSerializer
from template.models import ImageTemplate
......@@ -38,14 +35,3 @@ class ImageTemplateViewSet(ModelViewSet):
request.data.pop(key, None)
return super(ImageTemplateViewSet, self).update(request, partial=True)
@action(detail=True, methods=["post"])
def template(self, request, pk):
instance = self.get_object(pk)
serializer = InstanceFromTemplateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
new_template = ImageTemplate.create_from_instance(data["name"], data["description"],
instance, request.user)
serializer = ImageTemplateModelSerializer(instance=new_template)
return Response(serializer.data)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment