I have a model Inventory related to another model Product
class Inventory(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=0)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
whenever I create a "Product" I want to create a blank entry of "Inventory" as well.
How can I do that?
Views.py
class ProductCreateAPIView(CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = ProductSerializer
parser_classes = (FormParser, MultiPartParser)
def post(self, request, *args, **kwargs):
owner = request.user.pk
d = request.data.copy()
d['owner'] = owner
serializer = ProductSerializer(data=d)
if serializer.is_valid():
serializer.save()
print("Serializer data", serializer.data)
Inventory.objects.create(product=serializer.data['id'], quantity = 0, owner = owner) <= Will this work?
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Usually it is solved by using a signal, however, in this case you will need to add user to the product model like "added_by" to know who added the product and assign inventory owner to that product in your signal. It is not an option, then your request is not possible to implement, because user (owner) is only known in your view and your view will be the only place where you can create the default inventory.
Assuming you added added_by value to your product, your signal:
#receiver(post_save)
def create_default_product_inventory(sender, instance, created, **__):
if created and instance.added_by:
Inventory.objects.create(product=product, owner=instance.added_by)
To make it better, instead of the created flag, you can check if the product has added_by value, but not an inventory item, then create the default inventory item. This will make sure to create a default inventory whenever a user is assigned to a product.
As long as you need the user that created the product to be its inventory owner, you will need to save that user in the product details or just create the related models in the view directly.
Related
I am trying to create a rather simple model, all the model holds is a week number (as the primary key), and a oneToMany field with a list of users. The idea is that it should function like a schema, where you can see which users is attached to a specific week number. My problem is currently getting the serializer to work with the oneToMany field.
Model:
class Schema(models.Model):
week = models.PositiveIntegerField(primary_key=True,
unique=True,
validators=[MinValueValidator(1), MaxValueValidator(53)],
)
users = models.ForeignKey(MyUser, null=True, on_delete=models.SET_NULL)
class Meta:
ordering = ('week',)
Serializer:
class SchemaSerializer(serializers.Serializer):
class Meta:
model = Schema
fields = ('week', 'users')
def create(self, validated_data):
answer, created = Schema.objects.update_or_create(
week=validated_data.get('week', 1),
defaults={'users', validated_data.get('users', None)}
)
return answer
View:
class SchemaView(APIView):
permission_classes = (IsAuthenticated, IsAdminUser)
def get(self, request):
schemas = Schema.objects.all()
serializer = SchemaSerializer(schemas)
return Response(serializer.data)
def post(self, request):
data = request.data
serializer = SchemaSerializer(data=data)
serializer.is_valid()
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
I get the following error TypeError: cannot convert dictionary update sequence element #0 to a sequence. As I interpret that error, something is wrong with the first element (week number) when trying to do serializer.save().
I have a custom User model and a Group model that are linked by a UserGroup through model (Many to Many relationship):
models.py
class User(models.Model):
username = models.CharField(primary_key=True, max_length=32, unique=True)
user_email = models.EmailField(max_length=32, unique=False) # Validates an email through predefined regex which checks ‘#’ and a ‘.’
user_password = models.CharField(max_length=32)
user_avatar_path = models.CharField(max_length=64)
class Group(models.Model):
group_id = models.AutoField(primary_key=True)
group_name = models.CharField(max_length=32, unique=False)
group_admin = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='my_groups'
)
members = models.ManyToManyField(
User,
related_name='groups', # The name to use for the relation from the related object back to this one.
through='UserGroup' # Attaches a Junction table to the Many to Many relationship.
)
class UserGroup(models.Model): # Manually specified Junction table for User and Group
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='user_groups'
)
group = models.ForeignKey(
Group,
on_delete=models.CASCADE,
related_name='user_groups'
)
I'm trying to associate multiple users with a group, using a PATCH request to update the members attribute of a group. Using the following GroupSerializer, I'm able to associate a user as a member of the group when the group is created, by overriding the create function of the serializer:
serializers.py
class GroupSerializer(serializers.ModelSerializer):
members = MemberSerializer(many=True, required=False)
group_admin = serializers.SlugRelatedField(slug_field='username', queryset=User.objects.all()) # A Group object is related to a User object by username
class Meta:
model = Group
fields = ['group_id', 'group_name', 'group_admin', 'members']
def create(self, validated_data): # Overriden so that when a group is created, the group admin is automatically declared as a member.
group = Group.objects.create(**validated_data)
group_admin_data = validated_data.pop('group_admin')
group.members.add(group_admin_data)
return group
def update(self, instance, validated_data):
members_data = validated_data.pop('members') # Comes from the request body, gets the members list
#print('output: ' + str(members_data[0].items()))
add_remove = self.context['add_remove'] # Comes from the View
if members_data is not None:
if add_remove == 'add':
for member in members_data:
instance.members.add(member['username'])
elif add_remove == 'remove':
for member in members_data:
instance.members.remove(member['username'])
return super().update(instance, validated_data)
I'm not able to update the members associated with a group when overriding the update function of the serializer. The serializer is called from the following GroupUpdate view:
views.py
class GroupUpdate(generics.UpdateAPIView):
serializer_class = GroupSerializer
def get_object(self):
queryset = Group.objects.all()
group_id = self.kwargs['group_id']
if group_id is not None:
queryset = queryset.filter(group_id=group_id).first()
return queryset
def get_serializer_context(self): # Passes the URL paramters to the GroupSerializer (serializer doesn't have kwargs).
context = super().get_serializer_context()
context['add_remove'] = self.kwargs['add_remove']
print(self.request.data)
return context
def perform_update(self, serializer):
serializer=GroupSerializer(data=self.request.data, partial=True)
serializer.is_valid(raise_exception=True)
return super().perform_update(serializer)
Within the perform_update function of GroupUpdate, I receive the following: TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use members.set() instead. but I am unsure as to why this error would be raised, considering I was able to associate a user with a group in the create function in pretty much the same way.
This is what a PATCH request would have as the JSON body:
{
"members": [
{
"username": "small_man"
}
]
}
The output of self.request.data is {'members': [{'username': 'small_man'}]}.
You should specify instance of updated object when you create serializer otherwise serializer's save method will call create not update:
def perform_update(self, serializer):
instance = self.get_object()
serializer=GroupSerializer(instance, data=self.request.data, partial=True)
serializer.is_valid(raise_exception=True)
return super().perform_update(serializer)
BTW looks like perform_update is redundant and you can remove it since serializer validation should work without additional modifications.
I have a cart model and I want in API which if the user the add same items two times in cart the cart will automatically increase the quantity of service. In my case, if I add the same item twice it creates another cart instead of updating the previous one. I search for it a lot but I don't get an answer. I tried a lot to do this. If somebody is capable of giving answer then please give an answer , please
Here is my code:-
views.py
class CartViewSet(viewsets.ModelViewSet):
serializer_class = CartSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
user = self.request.user
if user.is_authenticated:
if user is not None:
if user.is_active and user.is_superuser or user.is_Customer:
return Cart.objects.all()
raise PermissionDenied()
raise PermissionDenied()
raise PermissionDenied()
filter_backends = [DjangoFilterBackend]
filterset_fields = ['date_created', 'user']
#action(detail=False)
def count(self, request):
queryset = self.filter_queryset(self.get_queryset())
count = queryset.count()
content = {'count': count}
return Response(content)
serializers.py
class CartSerializer(serializers.ModelSerializer):
class Meta:
model = Cart
fields = ['id','url', 'user', 'service', 'defects', 'date_created', 'quantity' , 'price', 'total']
models.py
class Cart(models.Model):
user = models.ForeignKey('accounts.User', related_name="carts", null=True, on_delete=models.SET_NULL)
quantity = models.IntegerField(default=1)
service = models.ForeignKey('accounts.SubCategory',null=True, on_delete=models.SET_NULL)
defects = models.ForeignKey('Defects',null=True, on_delete=models.SET_NULL)
price = models.IntegerField(default=False)
date_created = models.DateTimeField(auto_now_add=True)
total = models.IntegerField(blank=True, null=True)
def __str__(self):
return self.user.username
You have to override create method in you CartSerializer you can able to check and update if already created. I will mention some stuff tying this
Override Create method
class CartSerializer(serializers.ModelSerializer):
class Meta:
model = Cart
fields = ['id','url', 'user', 'service', 'defects', 'date_created', 'quantity' , 'price', 'total']
def create(self,validated_data):
""" Create cart if not created """
cart,created = Cart.objects.get_or_create(**validated_data)
if not created:
cart.quantity=cart.quantity+1
# you have to write your own logic i give you just an hint
return cart
Here we override create method it's invoking whenever we do post a request on the cart URL at the time we can change flow of creating an object of the cart
Hope you understand if you have any query concern let me know
I have discovered some best practices, below I will be showing a generalized way how to update API with function-based views which can be used with class bases views.
*** Note
you must have some id or unique field to identify the objects in the payload of JSON
Models.py
class Product(models.Model):
actual_product_id=models.Autofield(primary_key=True,auto_created=True)
price ....
etc
Serializers.py
from .import Product
class Product_Serializers(models.ModelSerializer):
class Meta:
model=Product
fields='__all__'
Raw Json format which will bw received by api
{ "some_product_id":"4163",
"price ": "41"
}
Urls.py
from django.conf.urls import url
url(r'^/api/productview',views,productview),
Views.py
#api_view(['PUT','GET','POST'])
def productview(request):
json_response=JSONParser().parse(request)
if "some_product_id" in json_response:
doctor_requested_id=json_response['some_product_id']
verified_object=Product.objects.filter(
some_product_id=actual_product_id)
verified_object.update(**json_response)
return JsonResponse({'message':"product has been updated
succesfully"},status=status.HTTP_200_OK)
else :
return JsonResponse({'message':"product's id dosen't exist"})
Hope it can solve the issue .
I'm working with a finance application, and due to the way that floating point math works, have decided to store all values in the database as cents (so dollar amount * 100). I have been banging my head against a wall to get the serializer to perform two calculations for me. On create/update accept a float value but then before saving to the database do value*100. Then on get, do value/100.
I got it half working using a SerializerMethodField, but that seemed to remove my ability to do create/update actions. I also at one point had something that kind of worked for create/update by changing the serializer.save() method in the view and adding an IntegerField validator on the field, but then that broke the SerializerMethodField.
In short, I'm stuck. lol
Here is my very simple model:
class Items(models.Model):
user = models.ForeignKey(
'CustomUser',
on_delete=models.CASCADE,
)
name = models.CharField(max_length=60)
total = models.IntegerField()
My views for this item:
class GetItems(generics.ListCreateAPIView):
serializer_class = ItemsSerializer
permission_classes = [permissions.IsAuthenticated, IsAuthorOrDenied]
user = serializers.HiddenField(default=serializers.CurrentUserDefault(), )
def get_queryset(self):
user = self.request.user
return Items.objects.filter(user=user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class SingleItem(generics.RetrieveUpdateDestroyAPIView):
serializer_class = ItemsSerializer
permission_classes = [permissions.IsAuthenticated, IsAuthorOrDenied]
user = serializers.HiddenField(default=serializers.CurrentUserDefault(), )
def get_queryset(self):
user = self.request.user
return Items.objects.filter(user=user)
def perform_update(self, serializer):
serializer.save(user=self.request.user)
And my serializer
class ItemsSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'name', 'budget_total')
model = models.Items
I feel like I should be doing more in my Serializer and less in my views, but that may be a completely different question all together.
Thanks in advance for the help!
Custom Serializer Field
You could write a custom field to handle the data:
class BudgetField(serializers.Field):
def to_representation(self, value):
# You can decide here how you want to return your data back
return value / 100
def to_internal_value(self, data):
# this will be passed to validated_data, so will be used to create/update instances
# you could do some validation here to make sure it is a float
# https://www.django-rest-framework.org/api-guide/fields/#raising-validation-errors
return int(data * 100)
Then use the custom field on your serializer.
class ItemsSerializer(serializers.ModelSerializer):
total = BudgetField()
class Meta:
fields = ('id', 'name', 'total')
model = models.Items
Override .update() and .create()
You could also choose to override these methods on the serializer.
class ItemsSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'name', 'budget_total')
model = models.Items
def create(self, validated_data):
# Modify validated_data with the value you need
return super().create(validated_data)
def update(self, instance, validated_data):
# Modify validated_data with the value you need
return super().update(instance, validated_data)
I am currently using restful and serializers to create and update my user.
Somehow I am not able to update some of the fields if the field has to do with OneToOneField / ForeignKey.
in my models.py, my Student is actually connected to the django build in user model which includes the user's email and connected to the school model which has the name of the school
class Student(Model):
user = OneToOneField(settings.AUTH_USER_MODEL, on_delete=CASCADE)
date_of_birth = DateField(blank=True, null=True)
student_name = CharField(max_length=256)
school = ForeignKey(School,
on_delete=CASCADE,
related_name="%(class)ss",
related_query_name="%(class)s",
blank=True,
null=True)
in serializer.py I have
class StudentSerializer(ModelSerializer):
user_email = SerializerMethodField()
school_name = SerializerMethodField()
class Meta:
model = Student
fields = (
'user_email', 'student_name', 'phone', 'school_name')
def get_user_email(self, obj):
return obj.user.email
def get_school_name(self, obj):
return obj.school.school_name
def create(self, validated_data):
return Student.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.user.email = validated_data.get('user_email', instance.user.email)
instance.student_name = validated_data.get('student_name', instance.student_name)
instance.phone = validated_data.get('phone', instance.phone)
instance.school.school_name = validated_data.get('school_name', instance.school.school_name)
instance.save()
return instance
in my view.py update function
class UserViewSet(ViewSet):
queryset = Student.objects.all()
def update(self, request, pk=None):
student = get_object_or_404(self.queryset, pk=pk)
serializer = StudentSerializer(student, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({'status': True})
return Response({'status': False, 'message': serializer.errors})
I am able to use the API view to pass in json and update the student_name and phone but as for the other two, user_email and school_name I am not able to update it. I don't get any error output when I submit the json though.
I realized the two fields that I am not able to update are because they OneToOneField / ForeignKey.
Can someone please give me a hand what I am missing here or what I can do to check?
Thanks in advance
I think your serializer isn't completed... the field of user and school is instance model, you need specific field in your serializer to implement the instance model, eg: with source='...' argument.
and example:
class VoteSerializer(serializers.ModelSerializer):
# by `username`
user = serializers.CharField(
source='user.username',
read_only=True
)
# by `pk/id`
candidate = serializers.IntegerField(
source='candidate.pk',
read_only=True
)
class Meta:
model = Vote
fields = ('user', 'candidate', 'score')
def create(self, validated_data):
return Vote.objects.create(**validated_data)
and in your case, perhaps is like this;
class StudentSerializer(ModelSerializer):
# by `pk/id` from the user
user = serializers.IntegerField(
source='user.pk',
read_only=True
)
school = serializers.IntegerField(
source='school.pk',
read_only=True
)
Since you are using SerializerMethodField which is readonly field (docs) for user_email and school_name so they won't be available in the validated_data.
Have you check the data you are receiving in validated_data
def update(self, instance, validated_data):
print('++'*22, validated_data)
return instance
The nested seriailzer / model / presentation actually helped me get the work done and pretty helpful.
An example is also provided here.
http://www.django-rest-framework.org/api-guide/serializers/#writing-update-methods-for-nested-representations
the above is continued from
http://www.django-rest-framework.org/api-guide/serializers/#writing-create-methods-for-nested-representations which contained how the nested serializer is being setup in the class and meta's fields