How to alphabetically order a many-to-many field? - django-models

I have an object Playlist that is defined in models.py:
class Playlist(models.Model):
"""Allow a user to create a customized list of songs."""
name = models.CharField(max_length=100)
image = models.ImageField(upload_to='playlists/%Y/%m/%d', blank=True, null=True)
songs = models.ManyToManyField('Song')
description = models.TextField(blank=True, null=True, max_length=1000)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""String for representing the model object."""
return self.name
def get_absolute_url(self):
"""Returns the url to access a detail record for this song."""
return reverse('playlist-detail', args=[str(self.id)])
I also have form, AddNewPlaylist that inherits from this model:
class AddPlaylistForm(forms.ModelForm):
class Meta:
model = Playlist
fields = ['name', 'image', 'description', 'songs']
I would like only the 'songs' object in the form to be ordered alphabetically as it appears in the form - how can I do this?
EDIT: I don't want to change the ordering of the song model in the database - only the way that it's ordered in the AddPlayList form.

So, I'm assuming that in your models.py, you have a model named Song. In the Song model, I am assuming you have a field song_name, or something like it. In Song, add:
class Meta:
ordering = ('song_name',)
This will order all querysets of Song objects alphabetically by their name, including the manytomany instance of your playlist.
If you want to order specifically the manytomany and not all Song querysets, then you should create a through model, like this:
class PlaylistSong(models.Model):
playlist = models.ForeignKey(Playlist, on_delete=models.CASCADE)
song = models.ForeignKey(Song, on_delete=models.PROTECT)
class Meta:
ordering = ('song__song_name',)
You can then replace the songs field in Playlist with:
songs = models.ManyToManyField(Song, through='PlaylistSong', blank=True)

Related

Got AttributeError when attempting to get a value for field `people` on serializer `commentsSerializer`

I am building a blog website and I am using Django rest framework
I want to fetch top 2 comments for a particular post along with their related data such as user details.
Now I have user details in two models
User
People
and the comments model is related to the user model using foreign key relationship
Models ->
Comments
class Comment(models.Model):
comment = models.TextField(null=True)
Created_date = models.DateTimeField(auto_now_add=True)
Updated_date = models.DateTimeField(auto_now=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE,
related_name='comments_post')
user = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='comments_user')
The People model is also connected to the user model with a foreign key relationship
People Model ->
class People(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,related_name='people')
Name = models.CharField(max_length=255,null=True)
following = models.ManyToManyField(to=User, related_name='following', blank=True)
photo = models.ImageField(upload_to='profile_pics', blank=True,null=True)
Phone_number = models.CharField(max_length=255,null=True,blank=True)
Birth_Date = models.DateField(null=True,blank=True)
Created_date = models.DateTimeField(auto_now_add=True)
Updated_date = models.DateTimeField(auto_now=True)
for fetching the comments I am using rest-framework and the serializers look like this
class UserSerializer(serializers.Serializer):
username = serializers.CharField(max_length=255)
class peopleSerializer(serializers.Serializer):
Name = serializers.CharField(max_length=255)
class commentsSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
comment = serializers.CharField(max_length=255)
Created_date = serializers.DateTimeField()
user = UserSerializer()
people = peopleSerializer()
The query to fetch the comments look like this ->
post_id = request.GET.get('post_id')
comments = Comment.objects.filter(post_id=post_id).select_related('user').prefetch_related('user__people').order_by('-Created_date')[:2]
serializer = commentsSerializer(comments, many=True)
return Response(serializer.data)
I am getting this error ->
Got AttributeError when attempting to get a value for field `people` on serializer `commentsSerializer`. The serializer field might be named incorrectly and not match any attribute or key on the `Comment` instance. Original exception text was: 'Comment' object has no attribute 'people'.
Unable to find a way out.
The source is user.people, not people, so:
class commentsSerializer(serializers.Serializer):
# …
people = peopleSerializer(source='user.people')
In the .select_related(…) [Django-doc] to can specify user__people: this will imply selecting user and will fetch the data in the same query, not in an extra query as is the case for .prefetch_related(…) [Django-doc]:
post_id = request.GET.get('post_id')
comments = Comment.objects.filter(
post_id=post_id
).select_related('user__people').order_by('-Created_date')[:2]
serializer = commentsSerializer(comments, many=True)
return Response(serializer.data)
Note: normally a Django model is given a singular name, so Person instead of People.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: created_date instead of Created_date.

How to add different many-to-many field into a single instance or row of a foreign key in django-rest-framework using model serializer

I am creating an ecommerce website using Django-rest-framework and react. I am trying to add items to cart. I am able to add items to the cart on the frontend but I want to store the cart data to the backend (Django) database so that whenever the user add items to cart and reloads the page the items should still be in his cart like any other ecommerce site. Here is my Code for django models, serializers, viewset.
class Products(models.Model):
title = models.CharField(max_length=100)
image = models.URLField()
description = models.TextField(max_length=500, blank=True, null=True)
category = models.CharField(max_length=50, blank=True, null=True)
rating = models.IntegerField(blank=True, null=True)
price = models.FloatField()
def __str__(self):
return f"{self.id}"
class Cart(models.Model):
product = models.ManyToManyField(
Products, related_name="cart")
buyer = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="cart")
def __str__(self) -> str:
return f"{self.buyer}"
class ProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = '__all__'
class CartSerializer(serializers.ModelSerializer):
class Meta:
model = Cart
fields = '__all__'
class ProductsViewSet(viewsets.ModelViewSet):
queryset = Products.objects.all()
serializer_class = ProductsSerializer
class CartViewSet(viewsets.ModelViewSet):
queryset = Cart.objects.all()
authentication_classes = [JWTAuthentication]
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = CartSerializer
router = routers.DefaultRouter()
router.register('products', ProductsViewSet, 'products')
router.register('cart', CartViewSet, 'cart')
I am using postman to post the cart items. I am able to add more than one products for a single buyer.
but the problem is when i again add another product to the same user using postman i added before, it creates another row for the same user.
I do not want that. I want a single instance or row of a user in cart table and add as many products as i want. when i post other products for the same user, that product should also get added up in the single user row or instance. What is the best way to achieve my goal.
Here is the issue, Django can't automatically do that because it doesn't know which behaviour is expected. If you look at the code to add a product and look at the code to add a cart, it's exactly the same. So behaviour will also be the same.
For the behaviour that you want, you will have to override the create method of your ModelViewSet.
Here are the steps to achieve what you want -
Check whether or not the user with that id already has a cart.
If they have a cart, then you'll need to fetch the cart object belonging to that user and add products to it.
If they don't, then you'll have to create a new cart object and do the default thing.
class CartViewSet(viewsets.ModelViewSet):
queryset = Cart.objects.all()
authentication_classes = [JWTAuthentication]
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = CartSerializer
def create(self, request):
# Checking whether the Cart object already exists for the buyer
cart = Cart.objects.filter(buyer_id = request.data.get("buyer"))
if len(cart)=1:
#If cart exists, we loop through the list of product ids and add them
for product_id in request.data.get("product"):
cart[0].product.add(get_object_or_404(Product, id = product_id ))
if len(cart)=0:
# Doing what normally happens.
return super().create(request)
if len(cart)>1:
#Error in database. One person having multiple carts. Custom error message.
To check out how to add data to many-to-many fields, check this out.

Adding data to multiple tables using django rest serializers

I am working on django rest framework coupled with react frontend. I am building a simple web service for a medium-sized organisation.
Here is my Invoice model from models.py
class Invoice(models.Model):
invoice_id = models.AutoField(primary_key=True, editable=False)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
date = models.DateField(auto_now_add=True)
builty_num = models.CharField(max_length=100)
total_amount = models.FloatField(validators=[MinValueValidator(0.0)])
class Meta:
ordering = ['date', 'customer']
InvoiceDetail from models.py
class InvoiceDetail(models.Model):
invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
warehouse = models.ForeignKey(Warehouse, on_delete=models.SET_NULL, null=True)
rate = models.FloatField(validators=[MinValueValidator(0.0)])
num_thaan = models.PositiveIntegerField()
gazaana_per_thaan = models.FloatField(validators=[MinValueValidator(0.0)])
Ledger model
class Ledger(models.Model):
TYPE = (
('D','Debit'),
('C','Credit'),
)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
date = models.DateField(auto_now_add=True)
detail = models.TextField()
transaction_type = models.CharField(max_length=1, choices=TYPE)
amount = models.FloatField(validators=[MinValueValidator(0.0)])
Now I will be having a form on my front-end which will be sending customer_id and an array of InvoiceDetail which I want to fill my database with. So, I will be using information from that form and use it to fill up multiple entries in my InvoiceDetail and the total_amount would also be calculated from the invoice detail. After this, I would also fill up the ledger based on
the total_amount. Right now, I am unable to figure out a way to do this as I am just able to manipulate a single table via a viewset
P.S. I am using these serializers right now with DefaultRouter()
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = '__all__'
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
class WarehouseSerializer(serializers.ModelSerializer):
class Meta:
model = Warehouse
fields = '__all__'
class ExpenseSerializer(serializers.ModelSerializer):
class Meta:
model = Expense
fields = '__all__'
class InvoiceSerialiser(serializers.ModelSerializer):
class Meta:
model = Invoice
fields = '__all__'
And this is my views.py
class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
class WarehouseViewSet(viewsets.ModelViewSet):
serializer_class = WarehouseSerializer
queryset = Warehouse.objects.all()
class ExpenseViewSet(viewsets.ModelViewSet):
serializer_class = ExpenseSerializer
queryset = Expense.objects.all()
In Django REST Framework (DRF), by using Nested Relationships of Serializer Relations data can be added to multiple tables having relationships.
In Nested Relationships, the referred entity can be embedded or nested in the representation of the object that refers to it. Such nested relationships can be expressed by using serializers as fields. If we want to support write-operations to a nested serializer field we need to have create() and/or update() methods in order to explicitly specify how the child relationships should be saved. It may be noted that by default nested serializers are read-only.
Below is the implementation of the relationship between Invoice and InvoiceDetail.
class InvoiceDetailSerialiser(serializers.ModelSerializer):
class Meta:
model = InvoiceDetail
fields = "__all__"
read_only_fields = ("invoice", )
It is required to make invoice readonly in InvoiceDetailSerialiser.
class InvoiceSerialiser(serializers.ModelSerializer):
invoice_details = InvoiceDetailSerialiser(many=True)
class Meta:
model = Invoice
fields = ('invoice_id', 'customer', 'date', 'builty_num', 'total_amount', 'invoice_details')
def create(self, validated_data):
invoice_details_data = validated_data.pop('invoice_details')
invoice= Invoice.objects.create(**validated_data)
for invoice_detail_data in invoice_details_data :
InvoiceDetail.objects.create(invoice=invoice, **invoice_detail_data )
return invoice
If the field is used to represent a to-many relationship, we should add the many=True flag to the serializer field.
Based on the above explanation, rest of the implementations can be developed.
More details on this topic from the official guide can be found here.

How can i add data into my model which is many to many field for another model

This is just a example of my models and form. my question is how i can save data in my both models only using one form. i wanted to save data through my Book Form() where in book model i have created a many to many field from Author Model and if i used this form then he is telling me to first add data in author model then only select and save data in book model. through my template i wanted to render fields from my form as author title , name , date of birth and book name and when user enter all the details it should be saved in respective models only.
TITLE_CHOICES = (
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
)
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['name', 'authors']

Django models - how to create a selected_instance field from an instance in a collection

Django noob questions:
I want to create a site which allows users to share info about cars. Each car should have a collection of images, and the submitter should select one of the images to be used to represent the car on a listing page. A basic set of models is shown below:
class Manufacturer(models.Model):
name = models.CharField(max_length=255)
class ModelBrand(models.Model):
name = models.CharField(max_length=255)
class Car(models.Model):
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)
# identifying information
manufacturer = models.ForeignKey(Manufacturer)
model_brand = models.ForeignKey(ModelBrand)
model_year = models.PositiveIntegerField()
class CarImage(models.Model):
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)
car = models.ForeignKey(Car, related_name='images')
source_url = models.CharField(max_length=255, blank=True)
image = ImageField(upload_to='cars')
But how do I model the selected image? Do I put a 'selected' BooleanField on the CarImage class? And how do I configure the Car and CarImage admin classes to allow an admin site user to select and image for a car from its 'images' collection?
First, I would like to suggest you to refactor your class using an auxiliary TimeStampedClass
class TimeStampedModel(models.Model):
"""
Abstract class model that saves timestamp of creation and updating of a model.
Each model used in the project has to subclass this class.
"""
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)
class Meta:
abstract = True
ordering = ('-created_on',)
So you can use this class over your project, subclassing it.
One simple solution for your question is attach your image gallery to your car, and create one attribute that is a IntegerField that stores the picture position in the image gallery:
...
class CarImage(TimeStampedField):
source_url = models.CharField(max_length=255, blank=True)
image = ImageField(upload_to='cars')
class Car(TimeStampedModel):
image_gallery = models.ManyToManyField(CarImage)
selected_picture = models.IntegerField(default=0)
# identifying information
manufacturer = models.ForeignKey(Manufacturer)
model_brand = models.ForeignKey(ModelBrand)
model_year = models.PositiveIntegerField()
So, if selected_picture is n, you just need to get n-th picture inside image_gallery

Resources