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.
Related
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.
I have two models with M2M relation. The custom table is defined as with en extra field
class DoctorHospital(models.Model):
clinic = models.ForeignKey(ClinicHospital, on_delete=models.CASCADE)
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
shift = models.CharField(max_length = 10)
Problem is that I am trying to fetch all clinics based on a specific doctor. Data is come based on specific doctor but custom field shift does not come.
here is my class base view
class DoctorDetailView(generic.DetailView):
model = Doctor
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['clinic_hospital_list'] = self.object.clinic_hospital.all()
return context
You can annotate the clinic_hospital_list to obtain the related shift value:
from django.db.models import F
class DoctorDetailView(generic.DetailView):
model = Doctor
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['clinic_hospital_list'] = self.object.clinic_hospital.annotate(
shift=F('doctorhospital__shift')
)
return context
Now the ClinicHospitals that arise from this queryset will have an extra attribute .shift that holds the value of the shift field in the intermediate table.
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)
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
I have the follow code example, which is a simplified abstraction of a real world project I'm working on:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class FeatureSet(models.Model):
"""
Feature Set
"""
name = models.CharField(max_length=255)
def __unicode__(self):
return u"%s" % self.name
class GenericObjectAlpha(models.Model):
title = models.CharField(max_length=255)
feature_sets = models.ManyToManyField(FeatureSet, through='Feature')
def __unicode__(self):
return u"%s" % self.title
class GenericObjectBeta(models.Model):
title = models.CharField(max_length=255)
feature_sets = models.ManyToManyField(FeatureSet, through='Feature')
def __unicode__(self):
return u"%s" % self.title
class Feature(models.Model):
"""
Feature
"""
# FK to feature set
feature_set = models.ForeignKey(FeatureSet)
# FK to generic object, Generic object alpha or beta... or others
content_type = models.ForeignKey(
ContentType,
default='article',
limit_choices_to={ 'model__in': ('genericobjectalpha', 'genericobjectbeta') },
related_name="play__feature_set__feature")
object_id = models.PositiveIntegerField(
"Feature object lookup")
content_object = generic.GenericForeignKey(
'content_type',
'object_id')
# Extra fields on a m2m relationship
active = models.BooleanField()
order = models.PositiveIntegerField()
def __unicode__(self):
return u"%s::%s" % (self.feature_set, self.content_object)
This line causes an error:
feature_sets = models.ManyToManyField(FeatureSet, through='Feature')
Obviously because the 'through' model lacks a corresponding FK to each side of the m2m. What I'd like to achieve here, is that one side of the m2m relationship is generic, and, that I can specify my own intermediary join table, to do the usual adding of custom fields etc.
What are my options for accomplishing this?
Note, its currently an important requirement to include the feature_sets = models.ManyToManyField(FeatureSet, through='Feature') line in the generic model, mostly for admin UI purposes. The reason why its generic is that its not yet determined how many models this line will be placed upon.