Saving OnetoOne field manually in django - django

I have following models:
class Person(User):
first_name = models.CharField(verbose_name=_('first name'), max_length=30)
last_name = models.CharField(verbose_name=_('last name'), max_length=30)
class Service(models.Model):
person = models.ForeignKey(Person, related_name='services')
price_from = models.DecimalField(_('price form'), max_digits=10, decimal_places=2, validators=[MinValueValidator(Decimal('0.01'))])
price_to = models.DecimalField(_('price to'), max_digits=10, decimal_places=2, validators=[MinValueValidator(Decimal('0.01'))])
class WorkPlace(models.Model):
service = models.OneToOneField('Service', related_name='work_place', primary_key=True)
city = CharField(verbose_name=_('city'), max_length=255)
street = CharField(verbose_name=_('street'), max_length=255)
I also registered Person in admin and made Service an inline admin.
Due to design, city and address are entered as multivalue field.
The problem is that I can't save WorkPlace manually.
Here are the forms:
class WorkPlaceForm(forms.ModelForm):
class Meta:
model = WorkPlace
fields = '__all__'
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = '__all__'
class ServiceForm(forms.ModelForm):
class Meta:
fields = '__all__'
model = Service
multi_work_place = MyMultiValueField()
def save(self, commit=True):
service = super(ServiceForm, self).save(commit=commit)
if self.cleaned_data['multi_work_place']:
work_place = WorkPlace(**{
'city': self.cleaned_data['multi_work_place'][0],
'street': self.cleaned_data['multi_work_place'][1],
})
# when there is brand new object is created, there is no service.pk
work_place.service = service # how???
work_place.save()
return service
Moreover, if I write service = super(ServiceForm, self).save(commit=True) on new object, this will raise Error as there is no Person created.
How can I solve this problem? Recall, that I'm working in admin.

class ServiceForm(forms.ModelForm):
class Meta:
fields = '__all__'
model = Service
multi_work_place = MyMultiValueField()
def save(self, commit=True):
service = super(ServiceForm, self).save(commit=False)
service.member_id = service.member.pk # here is the key command
service.save()
if self.cleaned_data['multi_work_place']:
work_place = WorkPlace(**{
'city': self.cleaned_data['multi_work_place'][0],
'street': self.cleaned_data['multi_work_place'][1],
})
# when there is brand new object is created, there is no service.pk
work_place.service = service # how???
work_place.save()
return service
I just needed to add member pk to service model

try this:
class ServiceForm(forms.ModelForm):
class Meta:
fields = '__all__'
model = Service
multi_work_place = MyMultiValueField()
def save(self, commit=True):
service = save_instance(self, self.instance, self._meta.fields,
fail_message, commit, self._meta.exclude,
construct=False)
service.save()
if self.cleaned_data['multi_work_place']:
work_place = WorkPlace(**{
'city': self.cleaned_data['multi_work_place'][0],
'street': self.cleaned_data['multi_work_place'][1],
})
# when there is brand new object is created, there is no service.pk
work_place.service = service
work_place.save()
return service

Related

django-rest-framework access field inside serializer

So I have a model like this
class DataSheet(BaseModel):
"""
Represents a single dataSheet.
dataSheets have their own model at the core. Model data is added to
the dataSheets in the form of separate records.
"""
class Meta:
verbose_name = 'datasheet'
verbose_name_plural = 'datasheets'
ordering = ['position', 'cluster']
required_db_features = {
'supports_deferrable_unique_constraints',
}
constraints = [
models.UniqueConstraint(
fields=['position', 'cluster'],
name='deferrable_unique_datasheet_position',
deferrable=models.Deferrable.DEFERRED
)
]
def __str__(self):
return self.name
objects = managers.DataSheetsManager()
positions = managers.PositionalManager()
position = models.PositiveSmallIntegerField(db_index=True, editable=True)
name = models.CharField(max_length=100, validators=[MinLengthValidator(2)], db_index=True)
description = models.CharField(max_length=1024, null=True, blank=True, db_index=True)
owner = models.ForeignKey('api_backend.Member', on_delete=models.CASCADE, db_index=True, editable=False)
fields = models.ManyToManyField('api_backend.Field')
overwrites = models.ManyToManyField('api_backend.RoleOverwrite')
parent = models.ForeignKey('api_backend.Category', on_delete=models.CASCADE, null=True, blank=True)
cluster = models.ForeignKey('api_backend.Cluster', on_delete=models.CASCADE, editable=False)
REQUIRED_FIELDS = [name, owner, cluster]
and a serializer like this
class DataSheetSerializer(serializers.ModelSerializer):
"""
A serialized DataSheet Object.
Datasheets have their own:
- array of fields
- array of role-overwrites
"""
def get_fields(self):
fields = super(DataSheetSerializer, self).get_fields()
fields['parent'].queryset = self.cluster.categories.all()
return fields
class Meta:
model = DataSheet
read_only_fields = ['position']
fields = '__all__'
# need to make sure that the parent category of the datasheet
# belongs to the datasheet's cluster only.
fields = partial.PartialFieldSerializer(many=True, read_only=True)
overwrites = partial.PartialOverWriteSerializer(many=True, read_only=True)
the thing is, I want to access the serializer model's cluster field inside of the get_fields method. However, I couldn't do the same. Can someone help me?
I've seen other answers involving initial_data, but that doesn't work here.
fields['parent'].queryset = self.cluster.categories.all()
cluster is an unresolved reference here.
self in get_fields is DataSheetSerializer instance not DataSheet model instance. hence it should not have cluster property. you can not access model DataSheet instance in get_fields as it gets fields from class DataSheet not from its instance. you can validate the field like
class DataSheetSerializer(serializers.ModelSerializer):
# ... other code
def validate(self, data):
parent = data.get('parent')
# check if parent is valid i.e in queryset
# if yes return data
# else raise serializers.validationError

Validation fail - field is required. ManyToMany serialization Django

I have form data that I want to serialize to create two objects, Account and AccountClub. AccountClub is the in between table between Account and Club with additional fields, rakeback and chip_value.
I can serialize the formdata but when i call the is.valid() function before saving, I get returned an error with the manytomany fields empty
Here is my models:
class Account(models.Model):
nickname = models.CharField(max_length=64)
club_account_id = models.IntegerField()
agent_players = models.ManyToManyField(
AgentPlayer, related_name="accounts")
clubs = models.ManyToManyField(
Club, through='AccountClub', related_name='accounts')
def __str__(self):
return f"{self.nickname} ({self.club_account_id})"
class AccountClub(models.Model):
account = models.ForeignKey(
Account, on_delete=models.CASCADE, related_name='club_deal')
club = models.ForeignKey(
Club, on_delete=models.CASCADE, related_name='account_deal')
rakeback_percentage = models.DecimalField(
max_digits=3, decimal_places=3, validators=[MinValueValidator(Decimal('0.01'))])
chip_value = models.DecimalField(max_digits=3, decimal_places=2, validators=[
MinValueValidator(Decimal('0.01'))])
def __str__(self):
return f"{self.account.nickname} belongs to {self.club.name} with rakeback of {self.rakeback_percentage} and chip value of {self.chip_value}"
Serializers:
class AgentPlayerSerializer(serializers.ModelSerializer):
class Meta:
model = AgentPlayer
fields = "__all__"
class ClubSerializer(serializers.ModelSerializer):
agent_players = AgentPlayerSerializer(many=True)
class Meta:
model = Club
fields = '__all__'
class AccountSerializer(serializers.ModelSerializer):
agent_players = AgentPlayerSerializer(many=True)
clubs = ClubSerializer(many=True)
class Meta:
model = Account
fields = [
'nickname',
'club_account_id',
'agent_players',
'clubs',
]
def create(self, validated_data):
rakeback_percentage = validated_data.pop('rakeback_percentage')
chip_value = validated_data.pop('chip_value')
club = validated_data.club
account = Account.objects.create(**validated_data)
account.account_club.rakeback_percentage = rakeback_percentage
account.account_club.chip_value = chip_value
AccountClub.create(account=account, club=club,
rakeback_percentage=rakeback_percentage, chip_value=chip_value)
return account
views.py:
def create_account(request):
data = FormParser().parse(request)
serializer = AccountSerializer(data=data)
if serializer.is_valid():
serializer.save()
next = request.POST.get('next', '/')
return HttpResponseRedirect(next, status=201)
return JsonResponse(serializer.errors, status=400)
your clubs field on the Account model is not blank=True so you can not create an account without at least a club. so you can not do
account = Account.objects.create(**validated_data)
and then do
AccountClub.create(account=account, club=club, rakeback_percentage=rakeback_percentage, chip_value=chip_value)
you may change your Account model code to:
clubs = models.ManyToManyField(Club, through='AccountClub', blank=True, related_name='accounts')
also checkout these links:
https://stackoverflow.com/a/6996358/6484831
https://stackoverflow.com/a/10116452/6484831

Insert data into intermediary table while submitting main modelform in Django

I have a Task model. I want to assign task to multiple users so i have taken ManytoMany relationship. So Django is creating a ManytoMany table but i want to track that which user has completed task and when. So I took intermediary model by using through='TaskComplete'. Now I can not see task_assign_to feild in form. And even i declare in modelForms and submit it gives below error.
Cannot set values on a `ManyToManyField` which specifies an intermediary model. Use audit.TaskComplete's Manager instead.
Now I want that admin selects the user from main form and into intermediary model.
I tried but can not find any solution for this. below is my code. Please guide me how to do it?
My Model:
class Task(models.Model):
task_audit_title = models.ForeignKey(MainAudit,on_delete= models.CASCADE, related_name='audit_title_for_task',verbose_name= ('Audit Title'))
task_subtask_name = models.ManyToManyField(SubTask, related_name='subtask_for_task',verbose_name= ('Subtask Title'))
task_subject = models.CharField(verbose_name= ('Task Subject'),max_length=100,blank=False)
task_description = models.CharField(verbose_name= ('Task Description'),max_length=1000,blank=True)
task_assign_to = models.ManyToManyField(User, related_name='task_assign_to', through='TaskComplete')
task_assign_by = models.ForeignKey(User,on_delete= models.CASCADE, related_name='task_crt_by')
task_deadline = models.DateTimeField(null=True,blank=True)
task_perticulars = models.ManyToManyField(Perticular, related_name='task_perticular', blank=True)
task_created_time = models.DateTimeField(default=timezone.now)
task_modified_by = models.ForeignKey(User,on_delete= models.CASCADE, related_name='task_mod_by', null=True, blank=True)
task_modified_time = models.DateTimeField(null=True,blank=True)
is_del = models.BooleanField(default=0)
class Meta:
permissions = (
("change_temp_delete_task", "Can delete temporarily"),
)
def __str__(self):
return self.task_subject
def get_absolute_url(self):
return reverse('create-task')
class TaskComplete(models.Model):
task_title = models.ForeignKey(Task,on_delete= models.CASCADE, related_name='assigned_task')
is_completed = models.BooleanField(default=0)
task_cmt_by_doer = models.CharField(verbose_name= ('Submit Comment'),max_length=100,blank=True)
completed_by = models.ForeignKey(User,on_delete= models.CASCADE, related_name = 'task_completed_by')
completed_time = models.DateTimeField(null=True,blank=True)
My View:-
class TaskCraeteView(LoginRequiredMixin,SuccessMessageMixin,CreateView):
# permission_required = 'Company.add_company'
model=Task
success_message = " Task Craeted successfully!"
reverse_lazy('create-task')
login_url = 'login'
template_name = 'create-task'
form_class = TaskCreateForm
# fields =[]
def form_valid(self,form):
form.instance.task_assign_by = self.request.user
My traceback my traceback link
My Form
class TaskCreateForm(forms.ModelForm):
class Meta:
model = Task
fields = ['task_audit_title','task_subtask_name','task_subject','task_description',
'task_assign_to','task_deadline','task_perticulars']

Django rest ModelViewSet many-to-many create

I have my model relationships as following: A Reader will have a Wishlist and a Wishlist will have many Books:
class Reader(models.Model):
user = models.OneToOneField(User)
...
# A library has many readers
which_library = models.ForeignKey('Library', related_name='readers', on_delete=models.CASCADE)
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=30)
...
# A library has many books
which_library = models.ForeignKey('Library', related_name='books', on_delete=models.CASCADE)
# Record the date whenever a new book is added, it will be helpful for showing new arrivals
when_added = models.DateTimeField(auto_now_add=True, blank=True, null= True)
reader = models.ManyToManyField('Reader', related_name='wishlist')
My serializers:
class ReaderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
password = serializers.CharField(source='user.password')
class Meta:
model = Reader
#fields = '__all__'
#depth = 1
fields = ('id', 'username', 'email', 'password', 'phone', 'address', 'dob', 'which_library')
def update(self, instance, validated_data):
...
instance.which_library = validated_data.get('which_library', instance.which_library)
instance.save()
return instance
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
user.set_password(user_data['password'])
user.save()
reader = Reader.objects.create(user=user, **validated_data)
return reader
class BookSerializer(serializers.ModelSerializer):
wishlist = ReaderSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = '__all__'
I can already perform CRUD operations with Reader, I want to now add books to a specific Reader's wishlist. My view:
class ReaderViewSet(viewsets.ModelViewSet):
serializer_class = ReaderSerializer
def get_queryset(self):
readers = Reader.objects.filter(which_library=self.kwargs.get('library_id'))
return readers
#detail_route(methods=['post'])
def wishlist(self):
return Response('OK')
URL that I hit:
router.register(r'readers/(?P<library_id>[0-9]+)', ReaderViewSet, base_name='readers')
Here I am expecting that on hitting api/readers/<library_id>/<book_id>/wishlist/addI will be able to perform
add operation to the Wishlist.
How can I achieve that?
You can use detail_route's argument url_path to change url of endpoint. Also you can add additional arguments like book_id directly to detail_routed method, so your method can look like this:
#detail_route(methods=['post'], url_path='(?P<book_id>[0-9]+)/wishlist/add')
def wishlist(self, library_id=None, book_id=None):
reader = self.request.user.reader
book = Book.objects.get(pk=book_id)
reader.wishlist.add(book)
return Response('OK')
And it should be accessible from api/readers/<library_id>/<book_id>/wishlist/add url.

Django Rest Framework: serializing/deserializing a calculated field

I just started using Django & Django REST framework. I have the following models:
class Account(models.Model):
name = models.CharField(max_length=100, blank=False)
vat_perc = models.DecimalField(max_digits=4, decimal_places=2)
def __str__(self):
return "ACCOUNT: {0} -- {1}".format(self.name, str(self.vat_perc))
class Entry(models.Model):
account = models.ForeignKey(Account)
description = models.CharField(max_length=100)
taxable_income = models.DecimalField(max_digits=10, decimal_places=2)
total_amount = models.DecimalField(max_digits=10, decimal_places=2, null=True)
def save(self, *args, **kwargs):
selected_vat = Account.objects.get(pk=self.account).vat_perc
self.total_amount = self.taxable_income * (100.00+selected_vat)/100.00
super(Entry, self).save(*args, **kwargs)
The idea would be to read the vat_perc value inside the account record the user has just selected and to perform a calculation to determine the total_amount value which then should be saved in the entry record on the database (I know some would regard this as suboptimal due to the duplication of data in the database; please follow me anyway).
The total_amount field should be regularly serialized when requested. Instead, the serializer should not do anything for deserialization, because the overriding of the save method in the model takes care of updating values if a creation or modification occurs. If I get the documentation correctly, all this means setting the total_amount field in the serializer class as read_only.
Now, these are my serializers:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ('id', 'name', 'vat_perc',)
class EntrySerializer(serializers.ModelSerializer):
class Meta:
model = Entry
fields = ('id', 'account', 'description', 'taxable_income', 'total_amount',)
total_amount = serializers.ReadOnlyField()
# alternatively: total_amount = serializers.FloatField(read_only=True)
But this is the error I get:
Got a TypeError when calling Entry.objects.create(). This may be because you have a writable field on the serializer class that is not a valid argument to Entry.objects.create(). You may need to make the field read-only, or override the EntrySerializer.create() method to handle this correctly.
Original exception text was: int() argument must be a string, a bytes-like object or a number, not 'Account'.
The last sentence sounds particularly obscure to me. Am I getting something wrong? Any hint?
Thanks in advance.
Thanks to Claudiu. Used SlugRelatedField in the serializer class and decimal.Decimaltype instead of float as I did mistankenly. The following code now works:
class Account(models.Model):
name = models.CharField(max_length=100, blank=False)
vat_perc = models.DecimalField(max_digits=4, decimal_places=2)
def __str__(self):
return "ACCOUNT: {0} -- {1}".format(self.name, str(self.vat_perc))
class Entry(models.Model):
account = models.ForeignKey(Account)
description = models.CharField(max_length=100)
taxable_income = models.DecimalField(max_digits=10, decimal_places=2)
total_amount = models.DecimalField(max_digits=10, decimal_places=2, null=True)
def save(self, *args, **kwargs):
self.total_amount = self.taxable_income * (decimal.Decimal(100.00) + self.account.vat_perc) / decimal.Decimal(100.00)
super(Entry, self).save(*args, **kwargs)
serializers.py
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ('id', 'name', 'vat_perc',)
class EntrySerializer(serializers.ModelSerializer):
class Meta:
model = Entry
fields = ('id', 'account', 'description', 'taxable_income', 'total_amount',)
total_amount = serializers.ReadOnlyField()
account = serializers.SlugRelatedField(queryset=Account.objects.all(), slug_field="vat_perc")