Django rest framework, update object after creation - python

I have a DRF API that takes in the following model:
class Points(models.Model):
mission_name = models.CharField(name='MissionName',
unique=True,
max_length=255,
blank=False,
help_text="Enter the mission's name"
)
# Some irrlevant feid
url = models.URLField(help_text='Leave Empty!', default=" ")
date_added = models.DateTimeField(default=timezone.now)
class Meta:
get_latest_by = 'date_added'
And it's serializer:
from rest_framework.serializers import HyperlinkedModelSerializer
from .models import Points
class PointsSerializer(HyperlinkedModelSerializer):
class Meta:
model = Points
fields = (
'id', 'MissionName', 'GDT1Latitude', 'GDT1Longitude',
'UavLatitude', 'UavLongitude', 'UavElevation', 'Area',
'url', 'date_added'
)
And the view:
class PointsViewSet(ModelViewSet):
# Return all order by id, reversed.
queryset = Points.objects.all().order_by('-id')
serializer_class = PointsSerializer
data = queryset[0]
serialized_data = PointsSerializer(data, many=False)
points = list(serialized_data.data.values())
def retrieve(self, request, *args, **kwargs):
print(self.data)
mission_name = self.points[1]
assign_gdt = GeoPoint(lat=self.points[2], long=self.points[3])
gdt1 = [assign_gdt.get_lat(), assign_gdt.get_long()]
assign_uav = GeoPoint(lat=self.points[4], long=self.points[5], elevation=self.points[6])
uav = [assign_uav.get_lat(), assign_uav.get_long(), assign_uav.get_elevation()]
area_name = f"'{self.points[-2]}'"
main = MainApp.run(gdt1=gdt1, uav=uav, mission_name=mission_name, area=area_name)
print('file created')
return render(request, main)
I want to update the URL field of the file to contain a constant pattern and format in the end the mission_name field.
object.url = f'127.0.0.1/twosecondgdt/{mission_name}'
How can that be achieved and where should I store such code, the views.py or serializers.py?

There are several ways this could be achieved based on your requirements.
If you want to set the url upon creation even if it is not through the api, you can do it in the save method of the model itself:
class Points(models.Model):
# fields here
def save(self, **args, **kwargs):
if not self.url.strip():
# You may want to store the value of `127...` in an environment variable
self.url = f"127.0.0.1/twosecondgdt/{self.mission_name}"
super().save(*args, **kwargs)
If you want to set it through the view/serializer, you can set it in the create method of your serializer:
class PointsSerializer(HyperlinkedModelSerializer):
def create(self, validated_data):
mission_name = validated_data["mission_name"]
validated_data["url"] = f"127.0.0.1/twosecondgdt/{mission_name}"
return super().create(validated_data)
You can also override some methods in your viewset like perform_create or create

Related

How to Override or Hide Django admin model form field value

Currently, I'm having a problem when overriding a form field value on my (Django==4.0.3) django admin form.
The objective is :
I have a specific user table that I'm connecting to AWS Cognito. And when the admin creates a new user in django, the system must create a request to create a new Cognito user.
Once the Cognito user is created it generates a "sub" code, and then the sub should be saved in django
Code Follows
Model
class BuyerUser(BaseModel):
buyer = models.ForeignKey(
Buyer, on_delete=models.RESTRICT, related_name="%(class)s_buyer"
)
cognito_sub = models.CharField(max_length=50)
given_name = models.CharField(max_length=50)
family_name = models.CharField(max_length=50)
preferred_username = models.CharField(max_length=50)
email = models.EmailField(blank=False)
terms_conditions_accepted_datetime = models.DateTimeField(null=True, blank=True)
def __str__(self):
return self.preferred_username
admin
class BuyerUsers(admin.ModelAdmin):
list_display = ('id', 'buyer', 'given_name', 'family_name', 'preferred_username', 'available')
list_filter = ('buyer', 'available',)
list_display_links = ('id', 'preferred_username',)
search_fields = ('buyer__name', 'preferred_username', 'available')
list_per_page = 20
form = BuyerUserChangeForm
add_form = BuyerUserAddForm # It is not a native django field. I created this field and use it in get_form method.
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during foo creation
"""
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
admin.site.register(BuyerUser, BuyerUsers)
and my forms
class BuyerUserAddForm(forms.ModelForm):
grupo = forms.CharField()
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, instance=None, use_required_attribute=None,
renderer=None):
super().__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, instance,
use_required_attribute, renderer)
self.cognito_sub = None
def save(self, commit=True):
grupo = self.cleaned_data.get('grupo', None)
self.given_name = self.cleaned_data.get('given_name', None)
self.family_name = self.cleaned_data.get('family_name', None)
self.preferred_username = self.cleaned_data.get('preferred_username', None)
self.email = self.cleaned_data.get('email', None)
cognito = CognitoDriver()
sub = cognito.parse_user(
cognito.create_user(self.preferred_username, self.email)["User"]
)["Sub"]
self.cognito_sub = sub
cognito.add_group(self.preferred_username, grupo)
return super(BuyerUserAddForm, self).save(commit=commit)
class Meta:
model = BuyerUser
# fields = '__all__'
exclude = ['terms_conditions_accepted_datetime']
class BuyerUserChangeForm(forms.ModelForm):
class Meta:
model = BuyerUser
fields = '__all__'elf.cognito_sub = sub
cognito.add_group(self.preferred_username, grupo)
return super(BuyerUserAddForm, self).save(commit=commit)
class Meta:
model = BuyerUser
# fields = '__all__'
exclude = ['terms_conditions_accepted_datetime']
class BuyerUserChangeForm(forms.ModelForm):
class Meta:
model = BuyerUser
fields = '__all__'
create
Change
This cognito sub field should have its value override after cognito-user is created. as it should be happening in the following code
cognito = CognitoDriver()
sub = cognito.parse_user(
cognito.create_user(self.preferred_username, self.email)["User"]
)["Sub"]
self.cognito_sub = sub
In fact, this cognito-user is being created and the sub is correct. the BIG PROBLEM is: this sub is not saved. It is getting only the value from the form.
I've tried to hide sub field using exclude = ['cognito_sub','terms_conditions_accepted_datetime']
but only happens to save a empty value.
You may ask why I use Forms instead of simply override model.Save() method
and the answer is: I need the grupo field, but this field must be persisted in DB. It only exists in Cognito.
You have to assign the value to form.instance instead of directly to the form itself.
class BuyerUserAddForm(forms.ModelForm):
grupo = forms.CharField()
# ...
def save(self, commit=True):
grupo = self.cleaned_data.get('grupo', None)
self.preferred_username = self.cleaned_data.get('preferred_username', None)
self.email = self.cleaned_data.get('email', None)
cognito = CognitoDriver()
sub = cognito.parse_user(
cognito.create_user(self.preferred_username, self.email)["User"])["Sub"]
self.instance.cognito_sub = sub
cognito.add_group(self.preferred_username, grupo)
return super(BuyerUserAddForm, self).save(commit=commit)
You might even want to disable the input field completly using the disabled attribute. https://docs.djangoproject.com/en/4.0/ref/forms/fields/#disabled
class BuyerUserAddForm(forms.ModelForm):
cognito_sub = forms.CharField(disabled=True)
# ...

Django Rest Framework: Use URL parameter in serializer

My goal is to use a URL parameter as a variable in a function and output it in the serializer. But the following solution doesnt work. Anybody know why?
The URL looks like this:
http://127.0.0.1:8000/v1/water-bodies/?from=2013-02-17&to=2013-02-18
models.py
class Application(models.Model):
"""Database models for application information"""
name = models.CharField(max_length=255)
machine_name = models.SlugField(max_length=255, allow_unicode=True)
description = models.TextField(blank=True, null=True)
indice_to_use = models.ForeignKey('Indice', on_delete=models.PROTECT, blank=True, null=True)
def __str__(self):
return self.name
views.py
class DetailView(APIView):
def get_serializer_context(self):
context = super().get_serializer_context()
context["date_from"] = self.request.query_params.get("from", None)
return context
def get(self, request, machine_name):
application = Application.objects.get(machine_name=machine_name)
serializer = OsdSerializer(application)
return Response({"Everything you need to analyzing "+application.name: serializer.data})
serializer.py
class OsdSerializer(serializers.ModelSerializer):
bands = BandSerializer(source='indice_to_use.needed_bands', many=True)
satellite = SatelliteSerializer(source='indice_to_use.satellite_to_use')
indice = IndiceSerializer(source='indice_to_use')
def get_alternate_name(self, obj):
return self.context.get('date_from')
class Meta:
model = Application
fields = ['machine_name', 'name', 'description', 'indice', 'satellite', 'bands', 'date_from', ]
We would be required to update the code at two places.
DetailView in views.py.
In this, we are updating context to include data_from. (please note, we can also access this directly in the serialzier)
class DetailView(APIView):
...
def get(self, request, machine_name):
application = Application.objects.get(machine_name=machine_name)
serializer = OsdSerializer(application, context={
"date_from": request.query_params.get("from")
})
return Response({"Everything you need to analyzing "+application.name: serializer.data})
...
OsdSerializer in the serializers.py
class OsdSerializer(serializers.ModelSerializer):
bands = BandSerializer(source='indice_to_use.needed_bands', many=True)
satellite = SatelliteSerializer(source='indice_to_use.satellite_to_use')
indice = IndiceSerializer(source='indice_to_use')
alternate_name = serializers.SerializerMethodField()
def get_alternate_name(self, obj):
return self.context.get('date_from')
class Meta:
model = Application
fields = ['machine_name', 'name', 'description', 'indice', 'satellite', 'bands', 'date_from', 'alternate_name']
Another approach would be to access the request object directly from the context of the serializer. By default, the serializer context contains the request object in it. To do so, just update the serializer as mentioned below
OsdSerializer in the serializers.py
class OsdSerializer(serializers.ModelSerializer):
bands = BandSerializer(source='indice_to_use.needed_bands', many=True)
satellite = SatelliteSerializer(source='indice_to_use.satellite_to_use')
indice = IndiceSerializer(source='indice_to_use')
alternate_name = serializers.SerializerMethodField()
def get_alternate_name(self, obj):
return self.context.get('request').query_params.get('from', None)
class Meta:
model = Application
fields = ['machine_name', 'name', 'description', 'indice', 'satellite', 'bands', 'date_from', 'alternate_name']
First you need to change from accessing the parameter from kwargs to request.GET instead.
If you have a url like this in django '/user/<int:user_id>' then you access through kwargs.
But if you send a url parameter, like this '/user/?user_id=1'. You access through the request object.
In your situation, I think rest_framework will send the request to the serializer automatically. So you can do something like this:
def get_alternate_name(self, obj):
date_from = self.request.GET.get('from')
date=self.context.get('request').parser_context.get('kwargs').get(
'edate')

Multiple Model File Uploader Using DRF

I have a models.py as the following and I'm trying to make multiple upload files in one request.
and the other fields in the model i put the values in the back-end so, what i need exactly how to to send array of data (files) in one request and handle the files and create record for every single files separate?
I also read a lot and see a lot of answers, but I felt the solution depends on the case maybe because I didn't got it will
please any one can help me ?
models.py
file_name = models.FileField(upload_to='docs/', null=True, blank=True)
created_by = models.ForeignKey(User, related_name='file_created_by', blank=True, null=True,on_delete=models.DO_NOTHING)
created_date = models.DateTimeField(auto_now_add=True)
serializers.py
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = File
fields = '__all__'
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
user = kwargs['context']['request'].user
super(FileSerializer, self).__init__(many=many, *args, **kwargs)
def create(self, validated_data):
validated_data['status'] = 'in_progress'
self.context["file_name"] = self.context['request'].FILES.get("file_name")
obj = File.objects.create(**validated_data)
return obj
views.py
class FileCreateAPIView(CreateAPIView):
queryset = File.objects.all()
serializer_class = FileSerializer
permission_classes = [IsOwnerOrReadOnly]
def get_queryset(self):
return File.objects.all()
def perform_create(self, serializer):
serializer.save(created_by=self.request.user, updated_by=self.request.user)
Update file_name model field's default serializer field to serializers.ListField and update the create method in serializer to loop over the list to create multiple objects.
Example:
class FileSerializer(serializers.ModelSerializer):
file_name = serializers.ListField(
child=serializers.FileField(
max_length=100000, # length of the file name
allow_empty_file=False,
use_url=False
),
write_only=True
)
class Meta:
model = File
fields = ('created_by', 'file_name', )
def create(self, validated_data):
files = validated_data.pop("file_name")
obj = None
# can also use `bulk_create`, if too many files
for file in files:
obj = File.objects.create(file_name=file, **validated_data)
return obj
NOTE: The serializer will now not output any file_name keys, if you use it for list usecase. For that, you can add another field fname or something as a serializers.SerializerMethodField and return the value of the file name.
Example:
class TestSerializer(serializers.ModelSerializer):
test_field = serializers.SerializerMethodField()
def get_test_field(self, obj):
# since your field would be a file, so you can access `name` attribute
return obj.test_field.name
fields = ('test_field', ...rest of the fields...)

Django Rest Framework : Got AttributeError when attempting to get a value for field `data_params` on serializer `OrderCreateSerializer`

This is my models:
class Order(models.Model):
"""
订单
"""
order_num = models.CharField(max_length=128, unique=True) # 订单编号
order_status = models.CharField(max_length=12) # 订单状态 "未支付", "已支付,未完成", "已完成", "已经删除","其他"
product_describe = models.TextField() # 产品描述
billing_type = models.CharField(max_length=16) # 计费类型
buytime = models.CharField(max_length=16) # 比如:1月 永久
count = models.IntegerField() # 购买数量
paytype = models.CharField(max_length=16) # 支付方式(支付包,微信,xxx)
cost = models.DecimalField(max_digits=8, decimal_places=2, default=0.00) # 费用(需要花费多少钱)
account = models.ForeignKey(to=Account) # 所属账户
ctime = models.DateTimeField(auto_now_add=True) # 创建时间
uptime = models.DateTimeField(auto_now=True) # 更新时间
def __str__(self):
return self.product_describe
def __unicode__(self):
return self.product_describe
This is my serializer:
class OrderCreateSerializer(ModelSerializer):
data_params = serializers.DictField() # 根据产品数据模型不同而异
class Meta:
model = Order
fields = (
"product_describe", # 产品描述 (购买xx产品 + 参数)
"billing_type", # 计费类型 ("包年包月")
# "buytime", # "购买时间"
# "count", # 数量
# "paytype", # 支付方式
"data_params", # 数据
)
def create(self, validated_data):
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
validated_data["order_num"] = generateOrderNum(userid=user.id)
validated_data["order_status"] = "未支付"
data_dic = validated_data.pop("data_params") #
validated_data["buytime"] = data_dic["data"]["buytime"]
validated_data["count"] = data_dic["data"]["count"]
validated_data["paytype"] = "" # 支付类型
validated_data["cost"] = 0.00 # 所需钱
validated_data["account"] = user.account # 是哪个账户
return Order.objects.create(**validated_data)
You see, in my serializer I have pop the data_params:
data_dic = validated_data.pop("data_params")
But when I access this API, I get:
AttributeError at /api/financialmanage/order/add/
Got AttributeError when attempting to get a value for field data_params on serializer OrderCreateSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Order instance.
Original exception text was: 'Order' object has no attribute 'data_params'.
If I don't pop data_params, I will get bellow error:
TypeError at /api/financialmanage/order/add/
'data_params' is an invalid keyword argument for this function
EDIT
My views.py:
class OrderSerializerCreateAPIView(CreateAPIView):
"""
create Order
"""
serializer_class = OrderCreateSerializer
permission_classes = []
queryset = Order.objects.all()
EDIT-2
In my case the data_params dictionary is necessary for me.
Because when I buy a product(such as CloudServer), which has count, vcpus, ram, disk, and bandwidth, I through the data_params to get that.
You may want to know why I must use data_params to receive the data, because, the product may be different, if the product is Wine, it can not have the vcpus property now.
I resolved it that same to yours:
data_params = serializers.DictField() # yours
data_params = serializers.DictField(write_only=True) # try it, pls.
the source code:
# rest_framework/serializers.py => L504
def to_representation():.
..
fields = self._readable_fields # this function rest_framework/serializers.py=>L371
...
class OrderCreateSerializer(ModelSerializer):
data_params = DictField(child=serializers.CharField())
.
.
.
def create(self, validated_data):
print(validated_data)
data_dic = validated_data.pop("data_params")
print(data_dic)
return super(OrderCreateSerializer, self).create(validated_data)
class OrderSerializer(ModelSerializer):
class Meta:
model = Order
fields = '__all__'
# by #Vasil Shtiliyanov if you want return data_parms after create
def to_representation(self, instance):
serialized_data = super(OrderSerializer, self).to_representation(instance)
serialized_data['data-params'] = #logic goes here
return serialized_data
class OrderSerializerCreateAPIView(CreateAPIView):
"""
create Order
"""
serializer_class = OrderCreateSerializer
permission_classes = []
queryset = Order.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer)
data = OrderSerializer(instance).data
headers = self.get_success_headers(data )
return Response(data , status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
return serializer.save()
If you want to add the data-params argument to the serializer when it is not a field in the model you should use the def to_representation function of DRF. Should look something like this:
def to_representation(self, instance):
serialized_data = super(SerializerClass, self).to_representation(instance)
serialized_data['data-params'] = #logic goes here
return serialized_data
And remove data-params from fields parameter of the Meta class.

JSONField serializes as json for POST, but string for GET

There is likely a very simple problem with my code, but I've been slamming my head against this problem for a couple days and can't make any headway.
Important Packages:
Django==1.11.3
django-cors-headers==2.1.0
djangorestframework==3.7.0
drf-nested-routers==0.90.0
psycopg2==2.7.3
pycparser==2.18
Here is what is happening:
I create a model via an AJAX call
My server correctly serializes the brainstorm_data field as a json object.
Now I navigate my user to the next page and fetch the current model
For some reason, brainstorm_data is now be returned as a string. Anytime I call a GET request on this resource I always get a string representation of the JSON object.
Here is the code associated:
models.py
from django.contrib.postgres.fields import JSONField
class Adventure(TimeStampedModel,
models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey(User)
world = models.ForeignKey(World)
theme = models.ForeignKey(Theme, default=1)
brainstorm_data = JSONField()
image_src = models.CharField(max_length=400, null=True, blank=True)
sentence_summary = models.TextField(null=True, blank=True)
paragraph_summary = models.TextField(null=True, blank=True)
page_summary = models.TextField(null=True, blank=True)
outline_complete = models.BooleanField(default=False)
brainstorm_complete = models.BooleanField(default=False)
private = models.BooleanField(default=False)
def __str__(self):
return self.name
views.py
class MyAdventuresViewSet(viewsets.ModelViewSet):
queryset = Adventure.objects.all()
serializer_class = AdventureSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return Adventure.objects.filter(user=self.request.user)
def create(self, request, *args, **kwargs):
user = self.request.user
world = World.objects.filter(user=user).first()
if not world:
world = World.objects.create(name='My World', user=user,
description="This is a default world we created for your adventures",
image_src=static('worlds/images/world_placeholder.png'))
data = request.data.copy()
data['user'] = user.pk
data['world'] = world.pk
data['theme'] = 1 # default theme
data['brainstorm_data'] = default_brainstorm
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
adventure = serializer.save()
Storyboard.objects.create(adventure=adventure, raw=default_storyboard['raw'], html=default_storyboard['html'])
return JsonResponse(serializer.data)
#detail_route(methods=['post'])
def complete_outline(self, request, pk):
adventure = Adventure.objects.get(pk=self.kwargs['pk'])
complete_adventure_outline(adventure)
serializer = self.get_serializer(data=adventure)
serializer.is_valid(raise_exception=True)
return JsonResponse(serializer.data, status=200)
#detail_route(methods=['post'])
def genres(self, request, pk):
genre_names = request.data
genre_models = Genre.objects.filter(name__in=genre_names)
adventure = self.get_object()
adventure.genre_set.set(genre_models)
adventure.save()
serializer = AdventureSerializer(adventure)
return JsonResponse(serializer.data)
serializers.py
class AdventureSerializer(serializers.ModelSerializer):
genre_set = serializers.StringRelatedField(many=True, read_only=True)
character_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
location_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
storyboard = serializers.PrimaryKeyRelatedField(read_only=True)
theme = serializers.PrimaryKeyRelatedField(queryset=Theme.objects.all())
class Meta:
model = Adventure
fields = '__all__'
mixins
# this is a dictionary used to default brainstorm data each time an adventure is created
default_brainstorm = {
"nodes": [...],
"edges": [...]
}
You can override the to_internal_value and to_representation in a new serializer field to handle the return data for JSON field.
class JSONSerializerField(serializers.Field):
"""Serializer for JSONField -- required to make field writable"""
def to_internal_value(self, data):
return data
def to_representation(self, value):
return value
And in turn, you would use this Field in a serializer:
class SomeSerializer(serializers.ModelSerializer):
json_field = JSONSerializerField()
class Meta:
model = SomeModelClass
fields = ('json_field', )
This should solve your problem :)
When I originally created the columns I did it with a different json field package. The base DB columns was actually text instead of json or jsonb. Creating new columns (django json fields), migrating the data, and then shifting the data back got my database back in a consistent order.

Categories

Resources