How to add a calculation in Django model? - django

I just want to be Per Amount to Paid Amount and result will be posted in balance automatic
this is my admin.py
#admin.register(StudentPaymentSchedules)
class StudentPaymentSchedulesAdmin(admin.ModelAdmin):
list_display = ('Fullname','Grade_Level','Payment_Schedule','Amount','Student_Payment_Date','Paid_Amount','Balance','Remarks',)
ordering = ('pk','Students_Enrollment_Records__Education_Levels')
search_fields = ('Students_Enrollment_Records__Student_Users__Firstname','Students_Enrollment_Records__Student_Users__Lastname',)
list_filter = ('Students_Enrollment_Records__Education_Levels',)
def Fullname(self, obj):
return obj.Students_Enrollment_Records.Student_Users
def Grade_Level(self, obj):
return obj.Students_Enrollment_Records.Education_Levels
actions = ["export_as_csv"]
def export_as_csv(self, request, queryset):
meta = self.model._meta
field_names = [field.name for field in meta.fields]
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
writer = csv.writer(response)
writer.writerow(field_names)
for obj in queryset:
row = writer.writerow([getattr(obj, field) for field in field_names])
return response
I have this models.py
class StudentPaymentSchedules(models.Model):
Students_Enrollment_Records=models.ForeignKey(StudentsEnrollmentRecord, related_name='+', on_delete=models.CASCADE,null=True,blank=True)
Payment_Schedule =models.CharField(max_length=500,null=True,blank=True)
Amount = models.FloatField(null=True,blank=True)
Student_Payment_Date = models.DateField(null=True, blank=True)
Paid_Amount = models.FloatField(null=True,blank=True)
Balance = models.FloatField(null=True,blank=True)
Remarks=models.TextField(max_length=500,null=True,blank=True)
for example if the Amount is 1,950 and the Paid Amount is 1500 if the admin click the Save button it will cocompute.

you can use pre-save signals to do it.
from django.db.models.signals import pre_save
def balance_reciever(sender, instance, *args, **kwargs):
Amount = instance.Amount
Paid_Amount = instance.Paid_Amount
Balance = Amount-Paid_Amount
instance.Balance = Balance
pre_save.connect(balance_reciever, sender=StudentPaymentSchedules)

You can use Django Admin save_model but I think you shouldn't create a separate column for a division result. Instead you should use #property function in your model class like this:
class StudentPaymentSchedules(models.Model):
Students_Enrollment_Records=models.ForeignKey(StudentsEnrollmentRecord, related_name='+', on_delete=models.CASCADE,null=True,blank=True)
Payment_Schedule =models.CharField(max_length=500,null=True,blank=True)
Amount = models.FloatField(null=True,blank=True)
Student_Payment_Date = models.DateField(null=True, blank=True)
Paid_Amount = models.FloatField(null=True,blank=True)
Remarks=models.TextField(max_length=500,null=True,blank=True
#property
def balance(self):
return self.Amount - self.Paid_Amount
And use SQL divide function if you need to get data directly from database.

Related

send request from signal

I have an exam model that whenever an instance is created, instances of the Question model to the number that is specified in the Exam are created(using post_save signal). Also, I have a Go code that whenever a request is sent, fills out 3 fields of the Question model. My problem is how can I send this request in the signal part.
The codes are as followed:
models.py:
class Exam(models.Model):
title = models.CharField(max_length=255)
subject = models.CharField(max_length=255, default='')
organizer = models.CharField(max_length=255, default='...')
description = models.TextField(max_length=1000)
created_at = models.DateTimeField(auto_now_add=True)
duration = models.DurationField()
number_of_questions = models.PositiveSmallIntegerField()
order = models.IntegerField(default=0)
def __str__(self):
return self.title
class ExamQuestion(models.Model):
exam = models.ForeignKey('ExamApply', on_delete=models.CASCADE)
question_template = models.ForeignKey(QuestionTemplate, on_delete=models.CASCADE)
text = models.TextField(max_length=5000, null=True, blank=True)
question_params = models.JSONField(null=True, blank=True)
answer_choices = models.JSONField(null=True, blank=True)
answer_given = models.JSONField(default=dict, null=True, blank=True)
correct_answer = models.JSONField(null=True, blank=True)
data = models.JSONField(null=True, blank=True)
is_correct = models.BooleanField(null=True)
order = models.IntegerField(null=True, blank=True)
def __str__(self):
return str(self.id)
class ExamApply(models.Model):
class Status(models.TextChoices):
CREATED = 'CR', 'Created'
STARTED = 'ST', 'Started'
FINISHED = 'FN', 'Finished'
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
start_date = models.DateTimeField()
end_date = models.DateTimeField()
status = models.CharField(max_length=2, choices=Status.choices, default=Status.CREATED)
def get_score(self):
score = ExamQuestion.objects.filter(exam=self, answer_given=F('correct_answer')).count()
return score
signals.py:
#receiver(post_save, sender=ExamApply)
def create_examapply_examquestion(sender, instance, created, **kwargs):
if created:
for _ in range(instance.exam.number_of_questions):
ExamQuestion.objects.create(exam=instance)
id = ExamQuestion.objects.all().last().id
return redirect('/question/' + str(id) + '/') #doesnt work
#receiver(post_save, sender=ExamApply)
def save_examapply_examquestion(sender, instance, created, **kwargs):
instance.exam.save()
urls.py related to the part I want:
urlpatterns = [
path('questions/<int:pk>/', UpdateQuestionAPI.as_view()),
]
views.py:
class UpdateQuestionAPI(generics.RetrieveUpdateDestroyAPIView):
queryset = ExamQuestion.objects.all()
serializer_class = IntegrateQuestionSerializer
lookup_field = 'pk'
def get(self, request, *args, **kwargs):
question = ExamQuestion.objects.filter(pk=kwargs['pk'])
serializer = ExamQuestionSerializer(question, many=True)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({"message": "updated successfully"})
else:
return Response({"message": "failed", "details": serializer.errors})
serializers.py:
class IntegrateQuestionSerializer(serializers.ModelSerializer):
class Meta:
model = ExamQuestion
fields = ['question_params', 'answer_choices', 'correct_answer',]
class ExamQuestionSerializer(serializers.ModelSerializer):
title = serializers.SerializerMethodField()
class Meta:
model = ExamQuestion
fields = ['title']
def get_title(self, obj):
return obj.exam.exam.title
I had the idea of using redirect(to the update view), but it doesn't work.
First of all, it makes no sense to use request in the signal part.
This code is related to the model and the orm and is not responsible of the request part. That's the view work.
The signal you are using is related to the creation of the ExamApply model. The view you posted doesn't create a ExamApply so it will not get called at all.
Just so you know, usually signals are avoided because they lead to complicated code. To quote the documentation “Signals give the appearance of loose coupling, but they can quickly lead to code that is hard to understand, adjust and debug. Where possible you should opt for directly calling the handling code, rather than dispatching via a signal.”
Here you have multiple issues in your signals, you have a return in a loop, that will stop the loop after the first iteration. Then you return an HttpResponse (in the form of redirect) but you are not in a view so it doesn't make sense.
It's not clear what you want to do because the view and the serializer you posted are not related to the creation of ExamApply as I understand it what you could do (one of):
Override the save method of ExamApply to create the questions.
Or handle the creation of questions in the view that create the ExamApply
For example:
def save(self, *args, **kwargs):
if not self.pk:
# not self.pk means a creation.
for _ in range(self.exam.number_of_questions):
question = ExamQuestion.objects.create(exam=self.exam)
# You might want to link question to user / exam apply here?
self.exam.question_set.add(question)
super().save(*args, **kwargs)
In your ExamApply creation view or serializer you might want to return the questions id, if you use DRF it's trivial to do so: https://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
But you might want to link questions to ExamApply and/or user and not just Exam.
On a side note, for the future, if you need the created object id you should use
question = ExamQuestion.objects.create(exam=instance)
question.id

How to save data from API to my MySQL database in Django REST project

I have a Django REST project. There is a model Product. I get some data from marketplace API about products stocks. And I need to save it to my datbase. I don't know, what kind of viewsets to choose. And how to make a create method. Thanks.
My Product model
`
class Product(models.Model):
store = models.ForeignKey(
Store,
on_delete=models.PROTECT, blank=True,
verbose_name="Store")
offer_id = models.CharField(max_length=50,
blank=True,
default="",
verbose_name="SKU")
name = models.CharField(max_length=128,
blank=True,
default="",
verbose_name="Name")
present = models.PositiveIntegerField(
default=0,
verbose_name="Present")
reserved = models.PositiveIntegerField(
default=0,
verbose_name="Reserved")
`
My serializer
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
store = serializers.CharField()
offer_id = serializers.CharField()
name = serializers.CharField()
present = serializers.IntegerField()
reserved = serializers.IntegerField()
The data I get from API is a list, for example:
[
{
"offer_id":"1-100-3-0",
"present":5,
"reserved":1
},
{
"offer_id":"1-101-3-9",
"present":0,
"reserved":0
}
]
Refer the doccumentation for more
here is how you do it
#api_view([ 'POST'])
def product_post(request):
if request.method == 'POST':
serializer = ProductSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
and urls should be
path('product_post/', views.product_post, name='product_post'),
I creat a Modelviewset, it works with out errors, but stocks aren't saved in DB.
I tried to create and run a test, but I have
self.assertEqual(len(Product.objects.filter(store__user_id=self.user.pk)), 1)
AssertionError: 0 != 1
So, I can't find a reason.
class ProductApi(ModelViewSet):
serializer_class = ProductSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return Product.objects.filter(
store__user_id=self.request.user.pk)\
.order_by('-offer_id')
def create(self, request, *args, **kwargs):
st = Store.objects.filter(user=self.request.user,
type=1)
for _ in st:
api = Parser(api_key=_.api_key,
client_id=_.client_id)
data = api.get_product_info_stocks()
if len(data) > 0:
for stock in data:
if Product.objects.filter(
offer_id=stock.get('offer_id')).exists():
pass
else:
stock_data = {
'offer_id': stock['offer_id'],
'present': stock['present'],
'reserved': stock['reserved']
}
Product.objects.create(**stock_data)
stocks = Product.objects.filter(
store__in=st).order_by(
'-offer_id')
s = ProductSerializer(stocks, many=True)
return Response(status=200, data=s.data)
else:
return Response(status=204,
data={"Info": "Empty response"})
return Response(status=400,
data={"Message": "User has no stores"})

Django REST Framework : serializer fields are missing in the response

I'm using Django 2.0 and Django REST Framework.
I have two models contact and transaction as below
contact model
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
amount given model
class AmountGiven(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
interest_rate = models.FloatField(blank=True, default=None, null=True, help_text='% of interest to be calculated')
_given_date = models.DateTimeField(
db_column='given_date',
default=timezone.now,
help_text='Date and time when amount was given to the contact'
)
def __str__(self):
return str(self.amount)
#property
def given_date(self):
return self._given_date
#given_date.setter
def given_date(self, value):
self._given_date = value
#property
def interest_to_pay(self):
if self.interest_rate:
datetime_diff = datetime.now(get_localzone()) - self.given_date
days = datetime_diff.days
duration_in_year = days/365
simple_interest_amount = (self.amount * duration_in_year * self.interest_rate)/100
return simple_interest_amount
return 0
#property
def total_payable(self):
return self.amount + self.interest_to_pay
#property
def amount_due(self):
returned_amount = 0
for returned in self.amountreturned_set.all():
returned_amount += returned.amount
return self.total_payable - returned_amount
and ContactSerializer
class ContactSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedRelatedField(
view_name='contacts:detail',
read_only=True
)
user = serializers.CurrentUserDefault()
amount_due = ReadOnlyField(source='amountgiven__amount_due')
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'full_name', 'amount_due')
and in views.py
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
But there is no field as amount_due and url in the response returned while making the request to /contacts/ endpoint with GET method.
Based on your comment, you want the sum of all the amounts(please edit your question). so you should use annotate in your queryset:
from django.db.models import Sum
def get_queryset(self):
return Contact.objects.filter(user=self.request.user).annotate(amount_due=Sum('amountgiven_set__amount'))
(I recommend using modelManager for the queryset and the filtering instead of doing it here)
and add a field like this to your serializer:
amount_due = serializer.IntegerFiled()
Your modeling doesn't allow you to access amount_due in the way which you'd like.
Your Contact model does not have amountgiven attribute.
It does however have amountgiven_set which you can use to obtain a queryset of amountgiven for given contact.
But there can be multiple ones so you need to decide which amount_due you want to display in your serializer.
You can use SerializerMethodField to serializer the value of amount_due which you would like:
class ContactSerializer(serializers.HyperlinkedModelSerializer):
amount_due = serializers.SerializerMethodField()
def get_amount_due(self, obj):
amountgiven = obj.amountgiven_set.first()
return amountgiven.amount_due
But again, as i already mentioned - amountgiven_set returns a queryset where you can have multiple objects.
In case you are sure you have only one for given contact, you can use first() as in my example to get it.

Making a custom id field in Django models

I'm trying to make a model in Django that has a custom id attribute. I want it to always equal the sum of 10000 and the current id number of that instance. How exactly do I write that? And do I have to do anything in the view?
Edit: I better put the code I'm using just in case.
models.py
class Schedules(models.Model):
course_name = models.CharField(max_length=128, choices=COURSE_NAME_CHOICES, default='a-plus')
location = models.CharField(max_length=128, choices=LOCATION_CHOICES, default='south_plainfield')
room = models.CharField(max_length=128, choices=ROOM_CHOICES, default='A')
start_date = models.DateField(auto_now=False, auto_now_add=False, default=datetime.date.today)
start_time = models.CharField(max_length=128, choices=START_TIME_CHOICES, default='eight-thirty am')
end_time = models.CharField(max_length=128, choices=END_TIME_CHOICES, default='eight-thirty am')
instructor = models.CharField(max_length=128, choices=INSTRUCTOR_CHOICES, default='adewale')
total_hours = models.CharField(max_length=128, choices=TOTAL_HOURS_CHOICES, default='six')
hours_per_class = models.CharField(max_length=128, choices=HOURS_PER_CLASS_CHOICES, default='four_and_half')
frequency = models.CharField(max_length=128)
status = models.CharField(max_length=128, choices=STATUS_CHOICES)
interval = models.CharField(max_length=128, choices=INTERVAL_CHOICES, default='1 day')
initiated_by = models.CharField(max_length=128, null=True)
schedule_id = models.IntegerField(default=0)
views.py
def start_One_Schedule(request):
form = ScheduleForm()
if request.method == 'POST':
form = ScheduleForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.initiated_by = request.user.username
obj.save()
return render(request, 'schedule/schedule.html', {})
else:
print(form.errors)
return render(request, 'schedule/start_one_schedule.html', {'form': form})
Create a property on the model that adds 10000 to the id field. You don't need to create the actual field.
class Example(models.Model):
#property
def custom_id(self):
return self.id + 10000
You can write custom id field in you model like this
class ABC(models.Model):
custom_id = models.IntegerField(default=0)
def save(self, flag=True, *args, **kwargs):
# Save your object. After this line, value of custom_id will be 0 which is default value
super(ABC, self).save(flag=True, *args, **kwargs)
# Here value of custom_id will be updated according to your id value
if flag:
self.custom_id = self.id + 10000
self.save(flag=False, *args, **kwargs)
flag is required as otherwise it will start recursion which will run infinitely.

Django admin does not show all entities

I've inherited an app created with Django. There is a problem with it: in admin interface, the page lists not all entities (videos), but some (16 of 25). I have no idea, what is this.
Then I run python manage.py shell, and there Video.objects.all(), there are all 25 objects (counted them using len and by iterating them with for loop).
I have found no managers or whatever (maybe I just don't know where to look for them).
On the bottom of admin page: 25 videos, while there are only 16 rows.
Then I add to VideoModelAdmin class list_per_page = 10, paginator show three pages, but only first two of them has any Videos, third shows no rows.
Here are some code.
# admin.py
class VideoModelAdmin(admin.ModelAdmin):
list_display = ['title', 'short_desc', 'author', 'redactor_choise', 'views_num', 'rating', 'is_published']
list_filter = ['is_published', 'redactor_choise']
list_per_page = 10
actions = ['make_published', 'update_comments_count']
exclude = ('file_lq', 'file_hq', )#'thumb',)
def make_published(self, request, queryset):
queryset.update(is_published=1)
make_published.short_description = "Опубликовать выделенные"
def save_model(self, request, obj, form, change):
instance = form.save(commit=False)
instance.author = request.user
instance.save()
return instance
def update_comments_count(self, request, queryset):
for video in queryset:
video.update_comments_count()
update_comments_count.short_description = "Пересчитать комментарии!"
# later there
admin.site.register(Video, VideoModelAdmin)
# models.py
class Video(models.Model):
def make_upload_path(instance, filename):
return 'video/thumbs/' + generate_random_name(filename)
category = models.ManyToManyField(Category, limit_choices_to = {'is_published': 1})
title = models.CharField(max_length=128)
short_desc = models.CharField(max_length=255)
long_desc = tinymce_models.HTMLField(blank=True)
file_lq = models.FileField(upload_to='video/lq/', null=True, blank=True)
file_hq = models.FileField(upload_to='video/hq/', null=True, blank=True)
thumb = models.FileField(upload_to=make_upload_path, blank=True, null=True)
#thumb = fields.ThumbnailField(upload_to=make_upload_path, sizes=settings.VIDEO_THUMB_SIZE, blank=True, null=True)
author = models.ForeignKey(User, editable=False)
redactor_choise = models.BooleanField(default=False)
views_num = models.SmallIntegerField(default=0, editable=False)
comments_num = models.SmallIntegerField(default=0, editable=False)
rating = models.SmallIntegerField(default=0, editable=False)
voters = fields.PickledObjectField(blank=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=False)
def get_absolute_url(self):
return "/video/%d" % self.id
def views_num_plus(self):
cursor = connection.cursor()
cursor.execute('update soctv_video set views_num=views_num+1 where id=%d', [self.id])
cursor.close()
def update_comments_count(self):
from threadedcomments.models import ThreadedComment as Comment
self.comments_num = Comment.objects.filter(video=self).count()
self.save()
#cursor = connection.cursor()
#cursor.execute('update soctv_video set comments_num = (select count(*) from soctv_comment where video_id = %s) where id = %s', [self.id, self.id])
#cursor.close()
def update_categories_counts(self):
cursor = connection.cursor()
cursor.execute('update soctv_category set num_items = (select count(*) from soctv_video_category where category_id = soctv_category.id)')
cursor.close()
def is_user_voted(self, uid):
try:
if self.voters[uid]:
return self.voters[uid]
except Exception:
return False
def increment_view_count(self, token):
import md5
token = md5.new(token).hexdigest()
if VideoView.objects.filter(uniquetoken=token).count() == 0:
VideoView(uniquetoken = token, video = self).save()
def view_count(self):
return self.views_num + VideoView.objects.filter(video=self).count()
def __unicode__(self):
return unicode(self.title)
The problem can be that some FK in some of your videos points to something that does not exist.
I had the same problem and this was the reason.
Django will silently fail if the value is not there in the foreign key column.
Add both null and blank attribute to the column
null=True, blank=True
Make sure that you are logged in to the correct account aswell.
In my case, My account did not have permissions to modify <Model>