Django Update serializer and unique constainst - python

I am trying to create a meal planner with react and django. I am having issues with the form and getting it to the backend. Right now I can submit the form from react and I get the data back to django correctly. Unless I need to add another mealItem for the same date and serviceType ('lunch', 'dinner'). I get the unique constraint error for the following fields: "meals_meal.menu_id, meals_meal.date, meals_meal.type, meals_meal.url".
I have an update function in the serializer that is not being called. What can I do to check for date and type if they exist update the data.
I need the lunch and dinners to be seperate even if they are from the same date.
Models.py
class Meal(models.Model):
menu = models.ForeignKey(
Menu,
related_name='meals',
on_delete=models.CASCADE,
)
date = models.DateField(db_index=True)
type = models.CharField(
choices=[
('lunch', 'Lunch'),
('dinner', 'Dinner'),
],
max_length=10,
)
url = models.URLField(max_length=200, default="")
created_at = models.DateTimeField(
auto_now_add=True,
editable=False,
)
updated_at = models.DateTimeField(
auto_now=True,
editable=False,
)
objects = MealQuerySet.as_manager()
class Meta:
unique_together = ['menu', 'date', 'type', 'url']
ordering = ['date', '-type']
class MealItem(models.Model):
meal = models.ForeignKey(
Meal,
related_name='items',
on_delete=models.CASCADE,
)
name = models.CharField(max_length=100)
type = models.CharField(
choices=[
('entre', 'Entre'),
('side', 'Side'),
('other', 'Other'),
],
max_length=10,
)
is_dairy_free = models.BooleanField(
default=False,
verbose_name='D',
help_text='Dairy Free',
)
is_gluten_free = models.BooleanField(
default=False,
verbose_name='G',
help_text='Gluten Free',
)
is_vegetarian = models.BooleanField(
default=False,
verbose_name='V',
help_text='Vegetarian',
)
created_at = models.DateTimeField(
auto_now_add=True,
editable=False,
)
updated_at = models.DateTimeField(
auto_now=True,
editable=False,
)
Serializers.py
class MealSerializer(serializers.ModelSerializer):
items = MealItemSerializer(many=True)
class Meta:
model = Meal
fields = ['id', 'date', 'type','url', 'items', 'menu', ]
validators = []
def create(self, validated_data):
item_data = validated_data.pop('items')
meal= Meal.objects.create(**validated_data)
for item_data in item_data:
MealItem.objects.get_or_create(meal=meal, **item_data)
return meal
def update(sel, instance, validated_data):
instance.id = validated_data.get('id', instance.id)
instance.date = validated_data.get('date', instance.date)
instance.type = validated_data.get('type', instance.type)
instance.save()
return instance
Views.Py
class MealViewSet(LoginRequiredMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = MealSerializer
pagination_class = MealPagination
def get_queryset(self):
if self.request.user.is_authenticated:
queryset = Menu.objects.all()
menu = get_object_or_404(
queryset, pk=self.kwargs['menu_pk'], users=self.request.user)
return Meal.objects.filter(menu=menu)
else:
print("not auth")
return HttpResponseRedirect(self.request, "/login")
def post(self, request, menu_pk):
data = self.request.data
user = self.request.user
if user.is_authenticated and user.has_perm("meals.change_menu"):
if request.method == "POST":
serializer =MealSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response({'success':"Your post was successfull."})
return Response({'failure': 'post was not authenticated'})
return Response({'failure': "user is not authenticated or does not have permission to submit form"})
def update(self, request):
data = self.request.data
user = self.request.user
if user.is_authenticated and user.has_perm("meals.change_menu"):
if request.method == 'PUT':
serializer = MealItemSerializer(instance=self.get_object(), data=data, partial=True )
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response({"Success": "Your meal was updated"})
This is the result I need to get. But right now I only submit one meal at a time in the items array. As oppposed to adding all three meals like I do in the django admin add meals pannel.
"id": 1,
"date": "2021-11-17",
"type": "lunch",
"url": "#ImageUrlFromFirebase",
"items": [
{
"name": "milk",
"type": "entre",
"is_dairy_free": false,
"is_gluten_free": false,
"is_vegetarian": true
},
{
"name": "beans",
"type": "side",
"is_dairy_free": false,
"is_gluten_free": true,
"is_vegetarian": false
},
{
"name": "sleep",
"type": "other",
"is_dairy_free": true,
"is_gluten_free": false,
"is_vegetarian": false
}
],
"menu": 1
},

The update method is called when the request type is PUT and corresponding to that there has to be an update method on your Viewset, which is missing it seems ? also , if an update is partial (that means only some fields are going to get updated ) then partial = True (has to be), for ref https://www.django-rest-framework.org/tutorial/2-requests-and-responses/
Ideally, I would create another viewset with the name MealItemViewset which will then have the update method on it , so we can separate creation, deletion and updating of Meals and MealItems

Related

"Incorrect type. Expected pk value, received User."

I have these two models: order and route. The route has a oneToMany relation with Order as you can see:
class Order(models.Model):
customer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='customer')
retailer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='retailer')
date_publish = models.DateField(default=datetime.date.today)
date_available = models.DateField()
weight = models.DecimalField(decimal_places=2, max_digits=5)
description = models.CharField(max_length=500, null=True)
route = models.ForeignKey(Route, related_name='orders', null=True, on_delete=models.CASCADE)
class Route(models.Model):
day = models.DateField()
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
start_time = models.TimeField()
When a route is created I want to associate orders with that route, so I've done the following serializer:
class routeSerializer(serializers.ModelSerializer):
orders = OrderSerializer(many=True)
class Meta:
model = Route
fields = ['day', 'warehouse', 'start_time', 'orders']
def create(self, validated_data):
orders_data = validated_data.pop('orders')
route = Route.objects.create(**validated_data)
for order_data in orders_data:
order_serializer = OrderSerializer(data=order_data)
order_serializer.is_valid(raise_exception=True)
orders = order_serializer.save()
orders.route = route
orders.save()
return route
class OrderSerializer(serializers.ModelSerializer):
ordertimelocation = orderTimelocationSerializer(many=True)
class Meta:
model = Order
fields = ['id', 'customer', 'retailer', 'date_available', 'weight', 'description', 'ordertimelocation']
def create(self, validated_data):
timelocations_data = validated_data.pop('ordertimelocation')
order = Order.objects.create(**validated_data)
for timelocation_data in timelocations_data:
order_time_location_serializer = orderTimelocationSerializer(data=timelocation_data)
order_time_location_serializer.is_valid(raise_exception=True)
order_time_location = order_time_location_serializer.save()
order_time_location.order = order
order_time_location.save()
return order
def update(self, instance, validated_data):
timelocations_data = validated_data.pop('ordertimelocation')
ordertimelocation = instance.ordertimelocation
for timelocation_data in timelocations_data:
order_time_location_serializer = orderTimelocationSerializer(data=timelocation_data)
order_time_location_serializer.is_valid(raise_exception=True)
order_time_location = order_time_location_serializer.save()
order_time_location.order = instance
order_time_location.save()
return instance
Views:
class GetRoutes(generics.ListAPIView):
queryset = Route.objects.all()
serializer_class = routeSerializer
class CreateRoute(generics.CreateAPIView):
queryset = Route.objects.all()
serializer_class = routeSerializer
class CreateOrder(generics.CreateAPIView):
queryset = Order.objects.all()
serializer_class = OrderSerializer
class GetOrders(generics.ListAPIView):
serializer_class = OrderSerializer
def get_queryset(self):
us = self.kwargs.get('us')
return Order.objects.filter(customer_id=us)
class GetOrder(generics.RetrieveAPIView):
serializer_class = OrderSerializer
def get_object(self, queryset=None, **kwargs):
item = self.kwargs.get('order')
return get_object_or_404(Order, id=item)
class UpdateOrder(generics.UpdateAPIView):
serializer_class = OrderSerializer
queryset = Order.objects.all()
Edit:
I also customised the default user model like this:
User models:
class UserManager(BaseUserManager):
def create_superuser(self, email, user_name, first_name, password, **other_fields):
other_fields.setdefault('is_staff', True)
other_fields.setdefault('is_superuser', True)
other_fields.setdefault('is_active', True)
if other_fields.get('is_staff') is not True:
raise ValueError(
'Superuser must be assigned to is_staff=True.')
if other_fields.get('is_superuser') is not True:
raise ValueError(
'Superuser must be assigned to is_superuser=True.')
return self.create_user(email, user_name, first_name, password, **other_fields)
def create_user(self, email, user_name, first_name, password, **other_fields):
if not email:
raise ValueError(_('You must provide an email address'))
email = self.normalize_email(email)
user = self.model(email=email, user_name=user_name, first_name=first_name, **other_fields)
user.set_password(password)
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
GENDER_MALE = 0
GENDER_FEMALE = 1
GENDER_OTHER = 2
GENDER_CHOICES = [(GENDER_MALE, 'Male'), (GENDER_FEMALE, 'Female'), (GENDER_OTHER, 'Other')]
email = models.EmailField(_('email address'), unique=True)
user_name = models.CharField(max_length=150, unique=True)
first_name = models.CharField(max_length=150, blank=True)
last_name = models.CharField(max_length=150, blank=True)
start_date = models.DateTimeField(default=timezone.now)
is_staff = models.BooleanField(default=False)
is_retailer = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
gender = models.IntegerField(choices=GENDER_CHOICES)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['user_name', 'first_name']
def __str__(self):
return self.user_name
def isretailer(self):
return self.is_retailer
User serizalizer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email', 'user_name', 'first_name', 'last_name')
Views:
class CustomUserCreate(APIView):
permission_classes = [AllowAny] #when a user create an account he isn't autenticated
def post(self, request, format='json'):
serializer = RegisterUserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
json = serializer.data
return Response(json, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ListUsers(generics.ListAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
class UserDetail(generics.RetrieveAPIView):
serializer_class = UserSerializer
def get_object(self, queryset=None, **kwargs):
item = self.kwargs.get('id')
return get_object_or_404(User, id=item)
I am sending a request like this:
{
"day" : "2021-12-12",
"warehouse": "1",
"start_time": "7:00",
"orders": [
{
"id": 15,
"customer": 1,
"retailer": 2,
"date_available": "2020-12-12",
"weight": "1.20",
"description": null,
"ordertimelocation": [
{
"longitude": "12.1223000000000000",
"latitude": "12.1223000000000000",
"time_interval": [
{
"start": "2021-07-21T10:10:00Z",
"end": "2021-07-21T10:10:00Z"
}
]
}
]
}
]
}
But the server returns a bad request:
{
"customer": [
"Incorrect type. Expected pk value, received User."
],
"retailer": [
"Incorrect type. Expected pk value, received User."
]
}
I'm new to django and I don't know what is a 'pk' value and why it is expecting it instead of User.
PK is primary key and in here is equal to id
For saving a record in db with it's relations, django needs PK of related object
But when you pass this pk to serializer, in validate() function of serializer, django if passed pk is existed in db and valid, if so, it would return it's model object
for example, you pass customer pk as 1, but after validation, there is a Customer object with id 1. and you are passing this object to order = Order.objects.create(**validated_data) but as i mentioned before, you should pass just PK
So one of solutions can be:
validated_data['customer'] = validated_data['customer'].id
validated_data['retailer'] = validated_data['retailer'].id
order = Order.objects.create(**validated_data)
And another solution is to overriding validate() function and control what to return

How to use serializer field and make response of the data from the choice field in django rest framework

How to use serializer field in DRF
models.py
class User(models.Model):
id = models.AutoField(primary_key=True)
first_name = models.CharField(max_length=255, null=True, unique=True)
birthdate = models.DateField(null=True, blank=False)
gender_choice = [
('male','male'),
('female','female')
]
gender = models.CharField(max_length=255, choices=gender_choice, default='male', null=True)
the rest ans is in the below
serializers.py
class UserRegSerializer(serializers.ModelSerializer):
show_me = serializers.SerializerMethodField('get_show_me') #the extra serializer field
class Meta:
model = User
fields = ('id', 'first_name', 'birthdate', 'gender')
def get_show_me(self, showmeobj): #this fuction returns the data
gender = getattr(showmeobj, 'gender')
if gender == 'male':
return 'Queen' #if user's gender is male (opposite gender)
else:
return 'King' #if user's gender is female (opposite gender)
view.py
class UserCreateAPIView(generics.CreateAPIView):
serializer_class = UserRegSerializer
queryset = User.objects.all()
def post(self, request):
first_name = request.data.get('first_name', False)
birthdate = request.data.get('birthdate', False)
gender = request.data.get('gender', False)
serializer = UserRegSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({
'status': True,
'message': 'Register User Successfully',
'data': serializer.data,
}, status = status.HTTP_201_CREATED)
else :
return Response({
'status': False,
'message': 'Error! something went wrong',
}, status = status.HTTP_400_BAD_REQUEST)
hope this answer will help you. changes accepted:)

DRF Error: Cannot assign must be a instance

I'm working on an app that lets you track your expenses and I'm trying to create an 'balnace' object when the user registers, but when I try so I get an Error: Cannot assign "15": "Balance.user_id" must be a "Users" instance.
Model
class Balance(models.Model):
balance = models.FloatField(
verbose_name="Balance", blank=False, null=False)
user_id = models.ForeignKey(
'expenseApi.Users', verbose_name="Usuario", blank=False, null=False, on_delete=models.PROTECT)
def __str__(self):
return '{}'.format(self.pk)
serializer
class RegisterSerializer(serializers.ModelSerializer):
tokens = serializers.SerializerMethodField()
email = serializers.EmailField(max_length=50)
class Meta:
model = Users
fields= ['id', 'email', 'password', 'name', 'lastname', 'birthday', 'tokens']
extra_kwargs = {
'password': {
'write_only': True,
},
}
def get_tokens(self,user):
refresh = RefreshToken.for_user(user)
data = {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
return data
def create(self, request):
email= Users.objects.filter(email=request['email'])
if email:
raise serializers.ValidationError({'detail': 'El email ya esta en uso'})
user = Users.objects.create_user(email=request['email'],
password=request['password'],
name=request['name'],
lastname=request['lastname'],
birthday=request['birthday'])
user['balance'] = Balance.objects.create(balance=0,user_id=user.id)
return user
I didn't go in detail about logic on your code but I guess error based on your code at
user['balance'] = Balance.objects.create(balance=0,user_id=user.id)
should be
user['balance'] = Balance.objects.create(balance=0, user_id=user)
Since you named your ForeignKey user_id, this means that you can assign a User object to .user_id, or the primary key to .user_id_id:
user['balance'] = Balance.objects.create(
balance=0,
user_id_id = user.id
)
I would however advise to rename user_id to user, since now the field "hints" that it is an id, but it is not.
class Balance(models.Model):
balance = models.FloatField(verbose_name='Balance')
user = models.ForeignKey(
'expenseApi.Users',
verbose_name='Usuario',
on_delete=models.PROTECT
)
# …
Then we thus can set this with:
user['balance'] = Balance.objects.create(
balance=0,
user_id = user.id
)

Update field in db in django from views with existing form, won't update because of "Integrity Error"

My first post, really newbie at programming. I am having issues to update a field in a form. I'll try to explain my best.
I have a Catalog class (products or services) with a couple fields, 2 of them must be unique.
models.py
class Catalogo(models.Model):
item = models.CharField(
max_length=100, help_text="Product or service name", unique=True
)
sku = models.CharField(max_length=50, help_text="Part Number", unique=True)
category = models.CharField(
max_length=15, choices=categoria, verbose_name="Category"
)
description = models.CharField(
max_length=200,
help_text="Item description",
verbose_name="Descripción",
)
created = models.DateTimeField(auto_now_add=True, help_text="Created")
updated = models.DateTimeField(auto_now_add=True, help_text="Updated")
active = models.BooleanField(default=True)
class Meta:
verbose_name = "product and service"
verbose_name_plural = "products and services"
def __str__(self):
return self.item
Then i have a form to gather the information
forms.py
categories = [("Product", "Product"), ("Service", "Service")]
class NewProductForm(forms.Form):
item = forms.CharField(
widget=forms.TextInput(attrs={"class": "form-control"}),
label="Item",
max_length=100,
)
sku = forms.CharField(
widget=forms.TextInput(attrs={"class": "form-control"}),
label="Part number",
max_length=50,
)
category = forms.ChoiceField(
choices=categories,
label="Category",
)
description = forms.CharField(
widget=forms.Textarea(attrs={"class": "form-control"}),
label="Item description",
)
Now for the views...i created 2 functions, one for adding new product/service and one for updating
views.py
def new_prod_serv(request):
new_form = NewProductForm()
if request.method == "POST":
new_form = NewProductForm(request.POST)
if new_form.is_valid():
new_product = Catalogo(
item=new_form["item"].value(),
sku=new_form["sku"].value(),
category=new_form["category"].value(),
description=new_form["description"].value(),
)
new_product.save()
return redirect("products-services")
else:
print(new_form.errors)
context = {"formulario": new_form}
return render(request, "SkytechnosaApp/comercial/nuevoproducto.html", context)
def update_prod_serv(request, primarykey):
product_service = Catalogo.objects.get(id=primarykey)
item = product_service.item
sku = product_service.sku
category = product_service.category
description = product_service.description
form_data = {
"item": item,
"sku": sku,
"category": category,
"description": description,
}
form = NewProductForm(initial=form_data)
if request.method == "POST":
form = NewProductForm(request.POST, initial=form_data)
if form.is_valid():
form.save()
return redirect("products-services")
else:
print(form.errors)
context = {"form": form}
return render(request, "SkytechnosaApp/comercial/nuevoproducto.html", context)
The html works okay, the problem i'm facing is when i click on edit...it will populate the form with the information of the product or service i want to edit (that's fine), but then i make the changes on the comment field for example (just want to update comment) and then I get the error IntegrityError at /comercial/productos/nuevo
UNIQUE constraint failed: Comercial_catalogo.sku
It's like it's trying to create another product, because when i go back and i edit all the fields, and click on save, i see another product created, but I just wanted to update, rather than create a new product....what i am missing?
Thank you!
Your form code you pasted in your question is not complete (it's just a form.Form not a form.ModelForm, and yet you called form.save())
forms.py
class NewProductForm(forms.ModelForm):
item = forms.CharField(
widget=forms.TextInput(attrs={"class": "form-control"}),
label="Item",
max_length=100,
)
sku = forms.CharField(
widget=forms.TextInput(attrs={"class": "form-control"}),
label="Part number",
max_length=50,
)
category = forms.ChoiceField(
choices=categories,
label="Category",
)
description = forms.CharField(
widget=forms.Textarea(attrs={"class": "form-control"}),
label="Item description",
)
class Meta:
model = Catalogo
After that, if you want to update an instance (instead of creating one), you have to tell the ModelForm which instance it has to update:
view.py
def update_prod_serv(request, primarykey):
...
instance = Catalogo.objects.get(id=primarykey)
if request.method == 'POST':
form = NewProductForm(request.POST, instance=instance) # you don't need to use `initial` since
if form.is_valid():
form.save()
...
That said, it's generally best to have just ONE view for creating and updating. If you want to see how that works let me know.

Add extra field in response output in DRF 3.0

I have the following models
class Restaurant(models.Model):
name_of_the_restaurant = models.CharField(max_length=30, blank=True)
opening_time = models.TimeField(auto_now=False, auto_now_add=False)
closing_time = models.TimeField(auto_now=False, auto_now_add=False)
And
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
city = models.CharField(max_length=30, blank=True)
country = models.CharField(max_length=30, blank=True)
postal_code = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
favourite_restaurant = models.ManyToManyField(Restaurant,
blank=True,
related_name='favourite_restaurant',
related_query_name='favourite_restaurant')
I have defined a serializer for Restaurant model which is mainly :
class RestaurantSerializer(serializers.ModelSerializer):
class Meta:
model = Restaurant
fields = '__all__'
Now in my ViewSet logic I am doing the following :
class RestaurantListView(generics.ListAPIView):
serializer_class = RestaurantSerializer
def get_queryset(self):
queryset = {'Error': 'Please pass valid url parameters'}
city = self.request.query_params.get('city', None)
postal_code = self.request.query_params.get('postalcode', None)
country = self.request.query_params.get('country', None)
if city is not None or postal_code is not None:
queryset = Restaurant.objects.filter(
Q(city=city) | Q(pincode=postal_code))
if country and city is not None and postal_code is None:
queryset = Restaurant.objects.filter(country=country, city=city)
return queryset
def get(self, request, format=None):
restaurant_qs = self.get_queryset()
ids_list = [restaurant.id for restaurant in restaurant_qs]
favourite_restaurant = is_favourite_restaurant(ids_list, self.request.user)
serializer = RestaurantSerializer(restaurant_qs, many=True)
return Response(serializer.data)
where is_favourite_restaurant is a custom function function which returns queryset of FAVOURITE restaurant(s) of a user. Now in the output for this GET request I am getting result as :
[
{
"id": 2,
"name_of_the_restaurant": "Aniket",
"opening_time": "14:08:33.413402",
"closing_time": "22:08:33.413414"
},
{
"id": 3,
"name_of_the_restaurant": "Aniket-1",
"opening_time": "14:13:37.656385",
"closing_time": "22:13:37.656397"
}
]
Whereas the desired output I want is to append an extra field is_favourite:true to that restaurant which user has previously marked favourite. And hence the output should be
[
{
"id": 2,
"name_of_the_restaurant": "Aniket",
"opening_time": "14:08:33.413402",
"closing_time": "22:08:33.413414",
"is_favourite": true,
},
{
"id": 3,
"name_of_the_restaurant": "Aniket-1",
"opening_time": "14:13:37.656385",
"closing_time": "22:13:37.656397"
}
]
EDIT :
Definition of is_favourite_restaurant function :
def is_favourite_restaurant(restaurant_qs, user):
favourite_restaurant_qs = Profile.objects.get(user=user).favourite_restaurant.filter(
pk__in=restaurant_qs.values_list('id', flat=True))
return favourite_restaurant_qs
You can use SerializerMethodField. SerializerMethodField allows add extra field which is read only as you want.
class RestaurantSerializer(serializers.ModelSerializer):
is_favorite = serializers.SerializerMethodField()
class Meta:
model = Restaurant
fields = ('your', 'fields', 'is_favorite')
def get_is_like(self, obj):
return is_favourite_restaurant(obj.id, self.context['request'].user)
Normally, ListAPIView add context to serializer. As you use your create method, you should add manually.
serializer = RestaurantSerializer(restaurant_qs, many=True, context={'request': self.request})
Context allows access some data which is we send from the view.
As you did not shown your is_favourite_restaurant, i can't say that what should you do in that function. I guess you should change ids parameter from array to one id.
Your response looks like
[
{
"id": 2,
"name_of_the_restaurant": "Aniket",
"opening_time": "14:08:33.413402",
"closing_time": "22:08:33.413414",
"is_favourite": True,
},
{
"id": 3,
"name_of_the_restaurant": "Aniket-1",
"opening_time": "14:13:37.656385",
"closing_time": "22:13:37.656397",
"is_favourite": False,
}
]
def is_favourite_restaurant(restaurant_id, user):
favourite_restaurant_qs = Profile.objects.get(user=user).favourite_restaurant.filter(
pk=restaurant_id).exists()
return favourite_restaurant_qs

Categories

Resources