Creating a nested class in django and running a helper method - python

I've the following User model,
class User(AbstractBaseUser, PermissionsMixin, Base):
username = models.CharField(max_length=255, null=False)
email = models.CharField(max_length=255, null=False, unique=True)
user_type = models.CharField(max_length=255, null=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
And this is my Meeting model
class Meeting(Base):
meeting_code = models.CharField(max_length=255)
owner = models.ForeignKey(User, related_name='meetings', on_delete=models.CASCADE, null=True, blank=True)
members = models.ManyToManyField(User, related_name='meeting_set', null=True, blank=True)
start_time = models.DateTimeField(auto_now_add=True)
end_time = models.DateTimeField(null=True)
When a meeting is created, I want to run this helper function to generate a meeting code
def generate_meetingid():
return ''.join(random.choices(string.ascii_uppercase, k=16))
This is my meeting serializer,
class MeetingSerializer(serializers.ModelSerializer):
owner = UserSerializer(required=True)
class Meta:
model = Meeting
fields = ['id', 'meeting_code', 'owner', 'members', 'start_time', 'end_time', ]
My questions is how do I write the Meeting View set that adds the creating user as the owner and also runs the helper method to create the meeting code.
In essence, I'm trying to complete this view,
class MeetingViewSet(viewsets.ModelViewSet):
queryset = Meeting.objects.all()
serializer_class = MeetingSerializer
def perform_create(self, serializer):
serializer.save()

The easiest way is to override the save method, while I usually prefer using pre_save signal. The logic is similar to generating a unique slug for model instance, the helper function is automatically invoked before you save the object. In your serializer you can simply mark meeting_code as read_only so the meeting_code is not used when creating or updating the instance during deserialization
# override save method
class Meeting(models.Model):
...
def save(self, *args, **kwargs):
# avoid meeting_code IntegrityError
# self._state.adding == True means item was not added in the database yet
if self._state.adding and not self.meeting_code:
self.meeting_code = generate_meetingid()
while Meeting.objects.filter(meeting_code=self.meeting_code).exists():
self.meeting_code = generate_meetingid()
super().save(*args, **kwargs)
# pre_save signal
#receiver(pre_save, sender=Meeting)
def generate_meeting_code(sender, **kwargs):
if kwargs.get('created', False):
# on meeting create
meeting=kwargs.get('instance')
meeting.meeting_code = generate_meetingid()
while Meeting.objects.filter(meeting_code=self.meeting_code).exists():
meeting.meeting_code = generate_meetingid()
Serializer
class MeetingSerializer(serializers.ModelSerializer):
owner = UserSerializer(required=True)
class Meta:
model = Meeting
fields = ['id', 'meeting_code', 'owner', 'members', 'start_time', 'end_time', ]
read_only_fields = ['meeting_code']
Views
class MeetingViewSet(viewsets.ModelViewSet):
queryset = Meeting.objects.all()
serializer_class = MeetingSerializer
def perform_create(self, serializer):
# pass owner to the serializer
serializer.save(owner=self.request.user)

Related

How to autopopulate a field in models.py

I have a class named Property with a field property_type, i wana autofill this field when i create one of the other models who have onetoone relationship with Property
I want the when i create a Apartment in django admin for exp the property_type in Property should autofill with the "Apartment", when i create Villa in django admin it should autofill with "villa"
class Property(models.Model):
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="agent_of_property")
district_id = models.ForeignKey(District, on_delete=models.CASCADE)
slug = models.SlugField(max_length=255, unique=True)
property_type = models.CharField(choices=PROPERTY_TYPE, max_length=20)
title = models.TextField(verbose_name="Titulli", help_text="Vendos titullin e njoftimit", max_length=500)
description = models.TextField(verbose_name="Pershkrimi", help_text="Vendos pershkrimin",max_length=1000)
address_line = models.CharField(verbose_name="Adresa", help_text="E nevojeshme",max_length=255)
price = models.IntegerField()
area = models.IntegerField()
views = models.IntegerField(default=0)
documents = models.CharField(verbose_name="Dokumentacioni", help_text="Dokumentat", max_length=255)
status = models.BooleanField(default=True)
activity = models.CharField(max_length=20, choices=ACTION_OPTION)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Prona"
verbose_name_plural = "Pronat"
def get_absolute_url(self):
return reverse("pronat:property_detail", args=[self.slug])
def __str__(self):
return self.title
class Apartment(Property):
property_id = models.OneToOneField(Property, on_delete=models.CASCADE, parent_link=True, primary_key=True)
floor = models.IntegerField(default=0)
room_num = models.IntegerField(default=0)
bath_num = models.IntegerField(default=0)
balcony_num = models.IntegerField(default=0)
class Meta:
verbose_name = "Apartament"
verbose_name_plural = "Apartamentet"
def save(self, *args, **kwargs):
self.property_type = "Apartment"
class Villa(Property):
property_id = models.OneToOneField(Property, on_delete=models.CASCADE, parent_link=True, primary_key=True)
living_room = models.IntegerField(default=1)
floors = models.IntegerField(default=1)
room_num = models.IntegerField(default=1)
bath_num = models.IntegerField(default=1)
balcony_num = models.IntegerField(default=0)
class Meta:
verbose_name = "Vila"
verbose_name_plural = "Vilat"```
I don't understand what your are doing. If you are inheriting from Property models in Apartment just remove OneToOneField and your save method would be
def save(self, *args, **kwargs):
self.property_type = 'Apartment'
super().save(*args, **kwargs)
But if you really want to use OneToOneField, replace Property inherit with models.Model and your save method should look like this.
def save(self, *args, **kwargs):
self.property_id.property_type = 'Apartment'
self.property_id.save()
super().save(*args, **kwargs)
For 2nd case you can also use django signal
from django.db.models.signal import post_save
# ...
def populate_property_type(sender, created, instance, **kwargs):
if sender.__name__ == 'Apartment':
instance.property_id.property_type = 'Apartment'
instance.property_id.save()
# condition for Villa
post_save.connect(populate_property_type, sender=Apartment)
post_save.connect(populate_property_type, sender=Villa)
I would suggest that you should add field in Appartment table with the name appartment_type and remove property_type from Property. But If you don't want to do that then Two ways are possible for your case.
1.When you are adding values from Django admin, you should also add the property_type from Admin.In that case you don't need to override save method.
Register your Property and Appartment Table in one view.
Make the admin Tables of Property and Appartment for admin view. Then register it like this way i-e
admin.site.register(PropertyAdmin, AppartmentAdmin)
and add values into both tables.
2: Simply override the save method and add this
def save(self, *args, **kwargs):
self.property_id.property_type = 'Apartment' # you should add other field in Appartment table just avoid hard coded 'Apartment'
self.property_id.save()
super().save(*args, **kwargs)

Add Extra fields in serializer from one to one relation models

In this project, I have two models Student and Parent related to each other through one to one field.
In Parent serializer, I want to add Students attributes like age. I am thinking of using SerializerMethodField for both cases is their any better way to do it?
I am not getting the queries on how to get the object attributes and little explanation would be great.
Here what I have done till now.
Models.py
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, default=None)
batch = models.ForeignKey(Batch, on_delete=models.CASCADE, null=True, related_name='students')
email = models.EmailField(null=True)
phone_number = models.CharField(max_length=10, null=True)
dob = models.DateField(blank=True, null=True, help_text="Enter in the following format : YYYY-MM-DD")
address = models.TextField(max_length=150, null=True)
age = models.IntegerField(blank=True)
image = models.ImageField(upload_to='profile_pictures', default='student_image.png', blank=True)
#property
def remarks(self):
return self.remark_set.all()
#property
def marks(self):
return self.marks_set.all()
def __str__(self):
return self.user.firstName + ' ' + self.user.lastName
class Parent(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, default=None)
child = models.ForeignKey(Student, on_delete=models.CASCADE)
email = models.EmailField(null=True)
phone_number = models.CharField(max_length=10, null=True)
address = models.TextField(max_length=150, null=True)
image = models.ImageField(upload_to='profile_pictures', default='student_image.png', blank=True)
def __str__(self):
return self.user.firstName + ' ' + self.user.lastName
Serilaizer.py
class ParentSerializer(serializers.HyperlinkedModelSerializer):
student_age = serializers.SerializerMethodField()
student_batch = serializers.SerializerMethodField()
parent_name = serializers.SerializerMethodField()
class Meta:
model = Parent
fields = "__all__"
def get_student_age(self, obj):
return Parent.objects.get(child__age = self.obj.user.child)
def get_student_batch(self, obj):
return Parent.objects.get(child__bacth = self.obj.user.child)
def get_parent_name(self, user):
return Parent.objects.get(user=self.request.user)
Views.py
class ParentView( mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet):
queryset = Parent.objects.all()
serializer_class = serializers.ParentSerializer
first way:
from apps.models import Student, parent
class BasicUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
class BasicStudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
class ParentSerializer(serializers.ModelSerializer):
user = BasicUserSerializer(read_only=True,many=False)
child = BasicStudentSerializer(read_only=True, many=True)
class Meta:
model = Parent
fields = '__all__'
you can do this way . its replace a serializer field that you want and if parent have several child then in child's field you have new all child's information as dictionary.
================================================================
second way is use HyperLinkModel .
class ParentSerializer(serializers.ModelSerializer):
user = serializers.HyperlinkedRelatedField(read_only=True,many=False)
child = serializers.HyperlinkedRelatedField(read_only=True, many=True)
class Meta:
model = Parent
fields = '__all__'
but notice that in first way you will have a independent serializer class that every time you need to serialize model class that related to User or Child you can use them simply.

Django restful create api marks a new entry as edited on creation

I have been working on tracking edit history of comments in my app using django-simple-history, I have been able to track the edited comments but just realized that am also tracking entries upon creation. ie new entries are added to the history table on creation.
my model
class Comments(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
body = models.TextField(max_length=200)
is_Child = models.BooleanField(default=False)
author = models.ForeignKey(User, related_name='author_rel',
on_delete=models.CASCADE)
article = models.ForeignKey(Article, related_name='comments',
on_delete=models.CASCADE, null=False)
parent = models.ForeignKey('self', null=True, blank=True,
on_delete=models.CASCADE, related_name='threads')
history = HistoricalRecords()
def __str__(self):
return str(self.body)
class Meta:
ordering = ['-created_at']
My view class
class CommentsListView(ListCreateAPIView):
serializer_class = CommentSerializer
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Comments.objects.all()
renderer_classes = (CommentJSONRenderer,)
def create(self, request, slug, *args, **kwargs):
serializer_context = {
'request': request,
'article': get_object_or_404(Article, slug=self.kwargs["slug"])
}
article = Article.objects.filter(slug=slug).first()
data = request.data
serializer = self.serializer_class(
data=data, context=serializer_context)
serializer.is_valid(raise_exception=True)
serializer.save(author=self.request.user, article_id=article.pk)
return Response(serializer.data, status=status.HTTP_201_CREATED)
That's part of django-simple-history behavior but what you could do is delete that type of records in a signal if you do not want to store that information.
from django.dispatch import receiver
from simple_history.signals import (
post_create_historical_record
)
#receiver(post_create_historical_record)
def post_create_historical_record_callback(sender, **kwargs):
history_instance = kwargs['history_instance']
if history_instance.get_history_type_display() == "Created":
history_instance.delete()

Getting attribute error when trying to access the nested serializer in Django Rest Framework

I am getting following error while using the PostSerializer:
Got AttributeError when attempting to get a value for field
full_name on serializer UserSerializer. The serializer field might
be named incorrectly and not match any attribute or key on the long
instance. Original exception text was: 'long' object has no attribute
'full_name'.
Serializers are as follows:
class PostSerializer(serializers.ModelSerializer):
author = UserSerializer(required=False, allow_null=True)
class Meta:
model = Post
fields = ('id', 'author', 'message', 'rating', 'create_date', 'close_date',)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'full_name',)
View:
class PostMixin(object):
model = Post
serializer_class = PostSerializer
permission_classes = [
PostAuthorCanEditPermission
]
queryset = model.objects.all()
def pre_save(self, obj):
"""Force author to the current user on save"""
obj.author = self.request.user
return super(PostMixin, self).pre_save(obj)
class PostList(PostMixin, generics.ListCreateAPIView):
pass
User model:
class User(AbstractBaseUser):
email = models.EmailField(unique=True)
username = models.CharField(max_length=40, unique=True, null=True)
full_name = models.CharField(max_length=50, blank=False)
phone = models.CharField(max_length=20, unique=True, null=True)
about = models.CharField(max_length=255, blank=True)
type = models.CharField(max_length=1, default='U')
is_active = models.BooleanField(default=True)
date_joined = models.DateTimeField(auto_now_add=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['full_name']
def __unicode__(self):
return self.email
def get_full_name(self):
return self.full_name
def get_short_name(self):
return self.full_name
Problem
Got AttributeError when attempting to get a value for field full_name on serializer UserSerializer.
The model User in Django has no such field called full_name.
There is though a method get_full_name() that does what you want.
Solution
So try using it through a SerializerMethodField
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username') # no full_name here
full_name = serializers.SerializerMethodField('get_full_name')
This will add a field called full_name to your serialized object, with the value pulled from User.get_full_name()
Check you are using your custom model and not Django's User model
You've customized your own User model, but since that models has full_name, you shouldn't have gotten that error in the first place, so double check you are not referencing Django's default User model first.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User # <--- Make sure this is your app.models.User,
# and not Django's User model
fields = ('id', 'username', 'full_name',) # This is OK on your User model
or just
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'first_name', 'last_name')

Django Rest Framework writable nested field using create()

I'm trying to write a create method that will write my nested fields but am finding that the nested object isn't written.
This is the sample I was using:
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ('username', 'email', 'profile')
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
But I'm failing to understand what the user=user refers to.
Here is my code:
class MessagesSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source='pk', read_only=True)
suggested_songs = SongSerializer()
class Meta:
model = Messages
fields = ('id','owner','url','suggested_songs',)
#fields = ('id','url','suggested_songs',)
def create(self, validated_data):
song_data = validated_data.pop('suggested_songs')
message = Messages.objects.create(**validated_data)
Song.objects.create(**song_data)
return message
class SongSerializer(serializers.HyperlinkedModelSerializer):
#id = serializers.IntegerField(source='pk', read_only=True)
class Meta:
model = Song
fields = ('id','title','artist','album','albumId','num_votes','cleared')
read_only_fields = ('song_id')
class Messages(models.Model):
owner = models.OneToOneField(User, primary_key=True, related_name='user_messages', editable=False) #TODO, change owner to 'To'
#suggested_songs = models.ManyToManyField(Song, related_name='suggested_songs')
suggested_songs = models.ForeignKey(Song, null=True, blank=True)
# If a user is added, this runs.
#receiver(post_save, sender=User)
def create_friend_for_user(sender, instance=None, created=False, **kwargs):
if created:
Messages.objects.get_or_create(owner=instance)
# Same as above, but for deletion
#receiver(pre_delete, sender=User)
def delete_friend_for_user(sender, instance=None, **kwargs):
if instance:
Messages.objects.get(owner=instance).delete()
class Song(models.Model):
"""
A model which holds information about the songs.
"""
#song_id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=150, blank=True, default='')
artist = models.CharField(max_length=150, blank=True, default='')
album = models.CharField(max_length=150, blank=True, default='')
albumId = models.CharField(max_length=150, blank=True, default='')
num_votes = models.IntegerField(default=0, blank=True)
cleared = models.BooleanField(default=False, blank=True)
class Meta:
ordering = ('title',)
#managed=True
I think that the issue might be in the MessageSerializer.create method:
def create(self, validated_data):
# here you are popping the suggested songs
song_data = validated_data.pop('suggested_songs')
# so when you create the message here the foreign key is set to NULL
message = Messages.objects.create(**validated_data)
# and here you create the Song instance correctly but it is not
# associated with the message
Song.objects.create(**song_data)
return message
You need to pass the foreign key to the Messages.create method like in the example you have.
def create(self, validated_data):
song_data = validated_data.pop('suggested_songs')
song = Song.objects.create(**song_data)
# song need to be created first because the foreign key is in
# the Messages model
message = Messages.objects.create(suggested_songs=song, **validated_data)
return message
I hope this helps!

Categories

Resources