one to one field update serialization issue - django-rest-framework

###################### Requirement #############################
/add_value/
payload:
{
"profile_id": "34AB",
"amt": 100.00
}
###################### Viewset ####################################
class cash_viewset(ModelViewSet):
queryset = Cashbalance.objects.all()
serializer_class = SampleSerializer
####################### Models ####################################
MOdel -1 ---
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
profile_id = models.CharField(max_length=50,unique=True)
MOdel -2 ---
class Cashbalance(models.Model):
profile = models.OneToOneField(Profile, on_delete=models.CASCADE,db_column="profile_id")
remianingAmount = models.FloatField()
amt = models.FloatField()
############################### Serializer ##############################
class SampleSerializer(serializers.ModelSerializer):
profile_id = serializers.CharField(source="profile.profile_id")
remianingAmount = serializers.FloatField(read_only=True)
class Meta:
model = CashBalance
fields = ["profile_id", "amt", "remianingAmount"]
def create(self, validated_data):
profile_dict = validated_data['profile']
userprofile = Profile.objects.get(profile_id=profile_dict['profile_id'])
bank_obj = CashBalance.objects.filter(profile_id=userprofile)
bank_obj.update(amt=validated_data['amt'], remianingAmount=validated_data['amt'])
return bank_obj[0]
def to_representation(self, instance):
data = super(serializers.ModelSerializer, self).to_representation(instance)
print(instance.profile.user.username)
result = {"profile_id": data['profile_id'], "name": instance.profile.user.username,
"remianingAmount": data['remianingAmount']}
return result
################################# Question is ######################
Assume that, there is already some data in user,profile and Cashbalance tables
As per the payload given above,Based on the profile_id ,I have to update amt value to 100, for this I have implemented the solution in create method(create method given above), please share your inputs ,I am hoping that update cannot be done in create method, how can i improve this in more good way according to my model designs by using modelviewset

To perform the update, you have to override the update method in the serializer.
def update(self, instance, validated_data):
instance.profile.profile_id
= validated_data["profile"].get('profile_id',instance.profile.profile_id)
instance.amt = validated_data.get('amt', instance.amt)
return instance
in your viewset
def update(self,request,pk=None):
profile_id = request.data.get("profile_id")
profile = Profile.objects.get(profile_id = profile_id)
ser = self.get_serializer_class()(instance=profile.cash_balance , data = request.data)
ser.is_valid(raise_exception=True)
ser.save()
in your CashBalanceModel, pass a related_name
profile = models.OneToOneField(Profile, on_delete=models.CASCADE,db_column="profile_id" , related_name="cash_balance")
check docs: https://www.django-rest-framework.org/api-guide/serializers/

Related

GET privacy(friends/public) on activities maintained on Django Rest Framework

Let's say i have a model
class User(models.Model):
username = models.CharField(max_length=20)
class Friends(models.Model):
friendship_creator = models.Foreignkey(user)
other_user= models.Foreignkey(user)
class Activity(models.Model):
PRIVACY = [
(Friends,Friends),
(Public,Public)
]
activity_creator = models.Foreignkey(User)
name = models.CharField(_("Activity Name"),max_length=100)
privacy = \
models.CharField(_("Privacy"),max_length=15,choices=PRIVACY)
now if a user creates activities with privacy set to friends and also creates some activities where privacy is set to public.So here how can i maintain the privacy in DRF ,,like only friends will be able to see privacy=friends activities and privacy=public can be seen by everyone
Demo View::
class ActivityListApi(ListAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ActivitySerializer
def get_queryset(self, *args, **kwargs):
user = self.request.user
activities = Activity.objects.all().order_by('id')
return activities
def get_queryset(self, *args, **kwargs):
user = self.request.user
friends = User.get_friends(user)
friends_id = friends.values_list('id',flat=True).distinct()
fr_activities = Activity.objects.filter(
privacy='Friends',creator_id__in=list(set(friends_id))
)
pub_activities = Activity.objects.filter(privacy='Public')
print(fr_activities,pub_activities)
activities = fr_activities | pub_activities
return activities
First, you need to set the related_name in Friends model so that you can get friends from the user model.
class Friends(models.Model):
friendship_creator = models.Foreignkey(user, related_name="friends_source")
other_user= models.Foreignkey(user, related_name="friends_target")
Second, you need to make the query to get the id array of friends.
target_friends = user.friends_source.other_user.values_list('id', flat=True).distinct()
source_friends = user.friends_target.friendship_creator.values_list('id', flat=True).distinct()
all_friends_ids = list(set(target_friends) | set(source_friends))
The activities should be either the privacy ones of friends or public ones. So finally, the code is like the following.
from django.db.models import Q
class ActivityListApi(ListAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ActivitySerializer
def get_queryset(self, *args, **kwargs):
user = self.request.user
queryset = Q(Q(activity_creator__id__in = self.get_friend_ids(user)) & Q(privacy = "Friends")) | Q(privacy = "public")
activities = Activity.objects.filter(queryset).order_by('id')
return activities
def get_friend_ids(self, user):
target_friends = user.friends_source.other_user.values_list('id', flat=True).distinct()
source_friends = user.friends_target.friendship_creator.values_list('id', flat=True).distinct()
return list(set(target_friends) | set(source_friends))

Django GraphQL Mutation Updated, but no change in Database

I created an update mutation as follows, with django==3.1.4 and graphene==2.1.8 :
# models.py
class CustomUser(AbstractUser):
# email = models.EmailField()
firebase_id = models.CharField(max_length=50, null=True)
nickname = models.CharField(max_length=50, null=True)
name = models.CharField(max_length=20, null=True)
gender = models.IntegerField(choices=Gender, default=3)
phone = models.CharField(max_length=20, null=True)
birthday = models.DateField(default=datetime(2020,1,1))
address = models.CharField(max_length=200, null=True)
profile_image = models.ImageField(default='default-avatar.png', upload_to='users/',
null=True, blank=True)
class UpdateMember(graphene.Mutation):
class Arguments:
firebase_id = graphene.String(required=True)
nickname = graphene.String()
name = graphene.String()
gender = graphene.Int()
phone = graphene.String()
birthday = graphene.Date()
address = graphene.String()
profile_image = graphene.String()
class Meta:
exclude = ["password"]
member = graphene.Field(MemberType)
success = graphene.Boolean()
# #login_required
#staticmethod
def mutate(root, info, firebase_id, **kwargs):
success = False
member_instance = CustomUser.objects.get(firebase_id=firebase_id)
if member_instance:
print(member_instance)
success = True
for k, v in kwargs.items():
member_instance.k = v
member_instance.save()
return UpdateMember(member=member_instance, success=True)
else:
return UpdateMember(member=None, success=False)
Running GQL below:
mutation {
updateMember(
firebaseId:"777",
name:"JJJJ")
{
success
}
}
Response:
{
"data": {
"updateMember": {
"success": true
}
}
}
But I checked the database, it seems no change in it, I think .save() should have done the work persisting changes to database......
Creating Member works fine. Using PostgresQL
Could anyone figure out why?
There is several issues in your code:
You can not assign your model fields using string like that. See this thread
for k, v in kwargs.items():
member_instance.k = v
member_instance.save()
Currently your member_instance.k has nothing to do with variable k inside for loop.
firebase_id field should be unique.
Currently you call CustomUser.objects.get(firebase_id=firebase_id) which is risky because firebase_id is not unique field. This may lead Multiple objects error if you have more than one CustomUsers saved with same id. To fix it, just define:
class CustomUser(AbstractUser):
# email = models.EmailField()
firebase_id = models.CharField(max_length=50, unique=True)
...
To check if your member_instance has really updated. You can for example print out the values before saving it and run some test cases before final implementation. For example:
if member_instance:
print(member_instance)
success = True
for k, v in kwargs.items():
member_instance.k = v
print(member_instance.k)
print(k)
print(getattr(member_instance, k))
member_instance.save()

Updating object using super().update()

I have an object, whose attributes I would like to update. Right now, I am able to update a single attribute eg: name. But the object has several attributes which include name, contact_name, contact_email and contact_phone_number.The following is what I have at the moment.
In views.py
class MerchantViewSet(GetPrefixedIDMixin, viewsets.ModelViewSet):
"""POST support for /merchants/."""
print ("in MerchantViewSet")
queryset = models.Merchant.objects.all()
serializer_class = serializers.CreateMerchantSerializer
lookup_field = "id"
# lookup_value_regex = f"{models.Merchant.id_prefix}_[a-f0-9]{32}"
lookup_value_regex = ".*"
permission_classes = [permissions.MerchantPermission]
def get_queryset(self):
"""Filter the queryset based on the full merchant name or starting with a letter."""
queryset = models.Merchant.objects.all()
search_param = self.request.query_params.get("search", None)
if search_param:
if search_param.startswith("^"):
queryset = queryset.filter(name__istartswith=search_param[1:])
else:
queryset = queryset.filter(name__icontains=search_param)
return queryset
def update(self, request, *args, **kwargs):
request.data["user"] = request.user.id
print (self.get_object().name)
print (request.data)
merchant = self.get_object()
print (response)
print (merchant.name)
for merchants in models.Merchant.objects.all():
print (merchants.id)
if (merchants.id == merchant.id):
merchants.name = request.data["name"]
merchants.save()
I've tried using
response = super(MerchantViewSet, self).update(request, *args, **kwargs)
from what I read in the DRF documentations but using this just returns error 404 when I run my test. Do I simply have to do with I did with name in my code above, with the other attributes? Or is there a more streamlined way to do this?
This is my test:
class MerchantsViewSetTest(tests.BigETestCase): # noqa
#classmethod
def setUpClass(cls): # noqa
super(MerchantsViewSetTest, cls).setUpClass()
cls.application = tests.get_application()
tests.create_group("merchant")
cls.consumer_user = tests.create_consumer()
cls.admin = tests.create_administrator()
cls.merchant_geraldine = models.Merchant.objects.create(
name="Test Account 1",
contact_name="Geraldine Groves",
contact_email="geraldine#example.com",
contact_phone_number="+35310000000",
)
cls. merchant_barbara = models.Merchant.objects.create(
name="Account 2",
contact_name="Barbara",
contact_email="barbara#example.com",
contact_phone_number="+35310000432",
)
def test_edit_merchant(self): # noqa
url = reverse("bige_transactions:merchant-detail", kwargs={'id': self.merchant_geraldine.prefixed_id})
# payload
data = {"name": "Edited"}
# Put data
resp_data = self.put(url, data, user=self.admin, status_code=200)
# Check if data was updated
url = reverse("bige_transactions:merchant-list")
# Try without authenticated user
self.get(url, status_code=401)
# Try with authenticated admin user
resp_data = self.get(url, user=self.admin, status_code=200)
print (resp_data)

Django Rest Framework - HyperlinkedRelatedField for abstract base class

My question is if is possible to add an HyperlinkedRelatedField in a serializer to get only the attributes of the base class. for example:
I want a json like that:
{
"modules": [
{
"moduleName": "M1: Fundamentos Técnicos",
"moduleDetails": "Bla bla bla.",
"moduleID": 0,
"userScore": 3,
"slides": [
{
"slideType": "Content",
"slideID": 0
},
{
"slideType": "Minigame1",
"slideID": 1
},
{
"slideType": "Video",
"slideID": 6
}
]
}
]
}
Here, Slide is the base class and Video and Minigame are subclasses.
Is possible this?, is possible add a HyperlinkedRelatedField for the url of each slide?.
Thanks in advance!
UPDATE!
this are my models:
class Module(TimeStampedModel):
moduleID = models.AutoField(primary_key=True)
moduleName = models.CharField(
max_length=100,
verbose_name='Nombre del modulo')
moduleDetails = models.TextField(verbose_name='Detalle')
moduleBullet1 = models.CharField(max_length=100, verbose_name='Punto 1')
moduleBullet2 = models.CharField(max_length=100, verbose_name='Punto 2')
moduleBullet3 = models.CharField(max_length=100, verbose_name='Punto 3')
moduleImageURL = models.ImageField(
upload_to="modulos", verbose_name='Imagen')
userScore = models.PositiveSmallIntegerField(
default=0, verbose_name='Score de usuario')
class Slide(TimeStampedModel):
CONTENT = 'Content'
MINIGAME = 'Minigame'
VIDEO = 'Video'
SLIDE_TYPE_CHOICES = (
(CONTENT, 'Contenido'),
(MINIGAME, 'Minigame'),
(VIDEO, 'Video'),
)
slideType = models.CharField(
max_length=20,
choices=SLIDE_TYPE_CHOICES,
default=CONTENT,
)
slideID = models.AutoField(primary_key=True)
slideOrder = models.PositiveSmallIntegerField(
verbose_name='Orden de visualizacion')
module = models.ForeignKey(Module, on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",)
class Meta:
abstract = True
class VideoContent(Slide):
videoURL = models.URLField(verbose_name='URL de video')
class Minigame(Slide):
timeToFail=models.PositiveSmallIntegerField()
And in my serializers.py i want something like this:
class SlideSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Slide
fields = ('url', 'slideType', 'slideID')
class ModuleSerializer(serializers.ModelSerializer):
slides = SlideSerializer(many=True, read_only=True)
class Meta:
model = Module
fields = ('moduleID', 'moduleName', 'moduleDetails', 'moduleImageURL', 'userScore', 'slides')
My view.py
class ModuleViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Module.objects.all()
serializer_class = ModuleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class SlideViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Slide.objects.all()
serializer_class = SlideSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Slide.Objects raise an error, AttributeError: type object 'Slide' has no attribute 'objects'
You can't query abstract base classes. Slice is an abstract class, you cannot use queryset = Slide.objects.all() in SlideViewSet because an abstract model isn't an actual database object, and therefore cannot be queried.
You have to choose between abstract inheritance, in which case there is no database relationship between the two classes, or multi-table inheritance, which keeps the database relationship at a cost of efficiency (an extra database join) for each query.
Using multi-table inheritance
class Slide(TimeStampedModel):
CONTENT = 'Content'
MINIGAME = 'Minigame'
VIDEO = 'Video'
SLIDE_TYPE_CHOICES = (
(CONTENT, 'Contenido'),
(MINIGAME, 'Minigame'),
(VIDEO, 'Video'),
)
slideType = models.CharField(
max_length=20,
choices=SLIDE_TYPE_CHOICES,
default=CONTENT,
)
slideID = models.AutoField(primary_key=True)
slideOrder = models.PositiveSmallIntegerField(
verbose_name='Orden de visualizacion')
module = models.ForeignKey(Module, on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",)
class VideoContent(Slide):
videoURL = models.URLField(verbose_name='URL de video')
class Minigame(Slide):
timeToFail=models.PositiveSmallIntegerField()
Now, your viewset:
class SlideViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Slide.objects.all()
serializer_class = SlideSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Documentation on multi-table inheritance:
multi-table inheritance uses an implicit OneToOneField to link the
child and the parent, it’s possible to move from the parent down to
the child
Hope this clears the things up.

django rest framework writable nested serializer returns empty nested data

I'm following this link (http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers) to write nested serializer. But when I pop the 'vars' from validated_data in the create method of HostSerializer, I found it's empty.
I'm using django 1.9.2 and django restframework 3.3.2.
My model:
class Host(models.Model):
name = CharField(max_length=20, primary_key=True)
vm_cpu = IntegerField(default=2)
vm_mem = IntegerField(default=2048)
create_vm = BooleanField(default=True)
def __unicode__(self):
return('%s' % (self.name))
class Variable(models.Model):
name = CharField(max_length=10)
value = CharField(max_length=20)
host = models.ForeignKey(Host, related_name='vars')
def __unicode__(self):
return('%s=%s' % (self.name, self.value))
Serializer
class VariableSerializer(ModelSerializer):
class Meta:
model = Variable
class HostSerializer(ModelSerializer):
vars = VariableSerializer(many=True)
class Meta:
model = Host
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
host = Host.objects.create(**validated_data)
for v in vars_data:
Variable.objects.create(host = host, **v)
return host
This is the problem I found vars_data is an empty list:
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
Here's the rest of the code
admin.py
class VariableAdmin(admin.ModelAdmin):
list_display = ['name', 'value']
class HostAdmin(admin.ModelAdmin):
list_display = ['name']
admin.site.register(Variable, VariableAdmin)
admin.site.register(Host, HostAdmin)
urls.py
router = DefaultRouter()
router.register(r'variables', VariableViewSet, base_name='variables')
router.register(r'hosts', HostViewSet, base_name='hosts')
urlpatterns = [
url(r'^', include(router.urls)),
]
views.py
class VariableViewSet(ModelViewSet):
queryset = Variable.objects.all()
serializer_class = VariableSerializer
class HostViewSet(ModelViewSet):
queryset = Host.objects.all()
serializer_class = HostSerializer
My test program
post.py
import json
import requests
file = 'host.json'
url = 'http://localhost:8001/test_nest/hosts/'
with open(file, 'r') as f:
j = f.read()
data = json.loads(j)
r = requests.post(url, data = data)
print r.text
And here's the test data
host.json
{
"name": "host4",
"vars": [
{
"name": "var2-a",
"value": "a1"
},
{
"name": "var2-b",
"value": "a2"
}
],
"vm_cpu": 2,
"vm_mem": 2048,
"create_vm": true
}
I'm new to django. So I'm wondering if it's something simple and obvious. Did I use the wrong viewset? Did I post to the wrong URL? Or I setup the URL structure wrong?
I your serializers try using...
def update(self, instance,validated_data):
instance.vars_data = validated_data.get('vars',instance.vars)
instance.host = Host.objects.create(**validated_data)
for v in vars_data:
v,created=Variable.objects.create(host = host, **v)
instance.v.add(v)
return host
The following code works for me. Maybe you can try it:
models.py
class UserProfile(AbstractUser):
pass
class TobaccoCert(models.Model):
license = models.CharField(primary_key=True, max_length=15)
license_image = models.ImageField(upload_to="certs", blank=True, null=True, verbose_name='cert image')
views.py
class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = UserRegSerializer
...
def create(self, request, *args, **kwargs):
data = request.data
# here i make my nested data `certs` and update it to data
image_data = request.FILES.get('images')
_license = data.get('username', None)
certs = {'license': _license, 'license_image': image_data}
data['certs'] = certs
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer) # save it
ret_dict = serializer.data
payload = jwt_payload_handler(user)
ret_dict["token"] = jwt_encode_handler(payload) # 获取token
ret_dict["name"] = user.name if user.name else user.username
headers = self.get_success_headers(serializer.data)
return Response(ret_dict, status=status.HTTP_201_CREATED, headers=headers)
serializer.py
class CertSerializer(serializers.ModelSerializer):
pass
class UserRegSerializer(serializers.ModelSerializer):
# [Serializer relations - Django REST framework](https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers)
# this line is vars of your code
certs = CertSerializer(many=True,write_only=True)
...
password = serializers.CharField(
style={'input_type': 'password'}, help_text="passowrd", label="passowrd", write_only=True,
)
class Meta:
model = User
fields = ("username", "mobile", "password", 'province', 'city', 'dist', 'code', 'certs')
def create(self, validated_data):
"""
"""
# here we get certs
certs_data = self.initial_data.get('certs')
# just pop certs because it is useless for user create
certs_empty = validated_data.pop('certs')
user = super().create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
# use certs data to create cert
image_url = self.save_image(certs_data)
_license = validated_data.get('username', None)
TobaccoCert.objects.create(license=_license, license_image=image_url)
return user
In short, you need to add your nested parameters directly in the request parameters. In my example code, it is certs which is vars in your example and then use certs_data = self.initial_data.get('certs') get the parameters you passed in create method.
Another possible way is post your data before your requests:
In postman:
enter image description here
in request:
python - Django Rest Framework writable nested serializer with multiple nested objects - Stack Overflow
In addition, you can try to modify queryDict directly. You can refer to this link and here
Some useful links
django - Need help understanding many and source fields in a serializer - Stack Overflow
python - Django rest framework writeable nested serializer data missing from validated_data - Stack Overflow

Resources