Displaying / Testing outputs are correct. Sanity Check - django

I am still learning Django and slowly improving but I have a few questions, I have my whole model below:
from django.db import models
from datetime import datetime, timedelta
# Create your models here.
year_choice = [
('year1','1-Year'),
('year3','3-Year')
]
weeksinyear = 52
hours = 6.5
current_year = datetime.year
class AdminData(models.Model):
year1 = models.IntegerField()
year3 = models.IntegerField()
#property
def day_rate_year1(self):
return self.year1 / weeksinyear / hours
class Price(models.Model):
name = models.CharField(max_length=100)
contract = models.CharField(max_length=5, choices=year_choice)
start_date = models.DateField(default=datetime.now)
end_date = models.DateField(default=datetime(2021,3,31))
def __str__(self):
return self.name
The main concern for me at the moment is trying to understand if my function def day_rate_year1(self): is working correctly, if someone could point me in the right direction to understand either how I display this as a string value in a template or test in the shell to see if the value pulls through as the values for year1 and year3 can change based on user input.
I am trying to work out the day rate so I can then use the start and end dates and work out the number of days between the two to calculate a price which is then displayed to the user which can again change depending on the number of days and the contract type which is a 3 year option or 1 year option.
Let me know if you need the views or templates as well.
Thanks for the help!

if someone could point me in the right direction to understand either how I display this as a string value in a template or test in the shell to see if the value pulls through as the values for year1 and year3 can change based on user input
if you launch a shell session as below, you should see the output of your property.
python manage.py shell
Expected output:
>>> from app_name.models import AdminData
>>> test = AdminData.objects.create(year1=2010, year3=2016)
>>> print(test.day_rate_year1)
5.946745562
>>>

Related

How do I limit requests per user in Django rest framework using throttling?

I'm working on a Quiz app and one question gets uploaded every week in a month (4 ques/month). Now I want to make sure that a logged in user can only attempt the question twice per week and not more than that. How can I do this using throttling or any other way?
Here's my Quiz model:
Days = (
("Mon", "Monday"),
("Tue", "Tuesday"),
("Wed", "Wednesday"),
("Thu", "Thursday"),
("Fri", "Friday"),
("Sat", "Saturday"),
("Sun", "Sunday")
)
class QuizDetail(models.Model):
name = models.CharField(max_lenght=255, blank=False, null=False)
start_date = models.DateTimeField()
end_date = models.DateTimeField()
publisehd_week_day = models.CharField(max_length=255, choices=Days)
The published_week_day can change every month, so basically one month it can be Tuesday and next month it can be Thursday.
Note: If in a month published_week_day is Tuesday and a user attempts last week's quiz on Monday and exhausts his two attempts then on Tuesday he should be able to attempt as it will be a fresh quiz.
Store it in another field
I am assuming end_date only stores the last time the quiz ended.
Create a field, say count. Now increase count by 1 everytime a test is attempted. If it reaches above 2, dont allow the quiz to be taken.
What about resetting it?
You could calculate it every single time you make the request. However this is clearly extremely ineffiecient. Therefore, I would do something like so:
Create another process.(Have a look at Simple approach to launching background task in Django)
That process constantly searches through the database at fixed intervals.
If it finds that a week has passed since the last time the user took the test, it resets the count to 0. To do this simple subtract current datetime(datetime.now()) with end_time and compare it to 2 weeks.
You have to create one model which basically would have a relationship with your QuizDetail model
class UserQuizAttempt(models.Model)
quiz = models.ForeignKey(QuizDetail, on_delete=models.CASCADE)
user = models.ForeginKey(User, on_delete=models.CASCADE)
attempt_made_on = models.DateTimeField(auto_now_add=True)
so in your views.py file, where the user will make an API call to attempt the quiz you have to check whether the user has made any attempt for that particular quiz in that particular week. If it is exceeding 2 then just return a response saying that he's exceeding the limit of the attempt for the week.
a basic example could look like this
from datetime import date
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
class QuizAPI(APIView):
authentication_classes = [SessionAuthentication]
permission_classes = [IsAuthenticated]
def check_user_attempts(self, request, quiz_instance):
current_week = date.today().isocalendar()[1]
attempts = UserQuizAttempt.objects.filter(attempt_made_on__week=current_week=current_week, quiz=quiz_instance, user=request.user)
if attempts.count() > 2:
return False
return True
def post(self, request):
quiz_id = request.data.get('quiz_id')
quiz_instance = QuizDetail.objects.get(id=quiz_id)
if self.check_user_attempts(request, quiz_instance):
UserQuizAttempt.objects.create(quiz=quiz_instance, user=request.user)
# your logic goes here
...
else:
return Response("Your attempts exceeding for the week! Please try next week", status=status.HTTP_406_NOT_ACCEPTABLE)
So with this, you will have the history of the user's attempt made on the quiz which can be used for reporting or something.

How to create specific django-model field, contains a date and unique number per day?

I create a internet-shop on django and have the following problem.
How to create a django model-field for order`s number, contains a date and unique number per day?
This option is described in the assignment and others will not suit me.
UPD:
order number should look like this: yyyymmdd + unique order`s num per day. The next day, this num will start from the beginning.
As a result, I made like this. Maybe someone will come in handy.
from django.db import models
from django.utils.timezone import datetime
def get_num(date=datetime.now()):
date = date.strftime('%Y%m%d')
if Order.objects.all().last():
last_order = Order.objects.all().last()
if last_order.number[0:8] == date:
num = int(last_order.number[8::]) + 1
else:
num = 1
else:
return f'{date}1'
return(f'{date}{num}')
class Order(models.Model):
number = models.CharField(max_length=12, default=get_num, verbose_name='номер заказа')
...

Django ORM: Conditional filtering

In a Django app, I keep daily scores of users in such a model:
class Score(models.Model):
user = models.ForeignKey(User)
score = models.PositiveIntegerField(default=0)
date = models.DateField(auto_now_add=True)
I want to find out the days when a user's score has changed drastically compared to a consequent day. That is, if for example, the user scores 10 times higher than the previous day.
How can I include such a condition in a query filter using Django ORM? Is it possible with a single query using conditional expressions as described here: https://docs.djangoproject.com/en/1.9/ref/models/conditional-expressions/
Thanks.
If you change your Score class slightly to include the previous day's score (which is admittedly pretty wasteful), you can pack the query into one line using F expressions.
Your new class:
class Score(models.Model):
user = models.ForeignKey(User)
score = models.PositiveIntegerField(default=0)
lastscore = models.PositiveIntegerField(default=0)
date = models.DateField(auto_now_add=True)
Then the filter becomes:
from django.db.models import F
daily_chg = 10
big_changes = Score.objects.filter(score__gt=daily_chg*F('lastscore'))
Instead of using timedeltas to search for and set the previous day's score field, I'd look into establishing an ordering via a Meta class and calling latest() when saving the current day's score.
Using timedelta we can test for the last week's days for a given user as such:
from my_app.models import Score
import datetime
def some_view(request):
days_with_score_boost = []
today = datetime.date.today()
for each_day in xrange(0,7):
day_to_test, day_before_to_test = today - datetime.timedelta(days=each_day), today - datetime.timedelta(days=each_day + 1)
day_before_score = Score.objects.get(user=request.user,date=today - datetime.timedelta(days=each_day)).score # will need to catch the exception raised if .get return None and process appropriately
if Score.objects.filter(user=request.user,score__gte=days_before_score * 10,date=day_before_to_test).count() > 0:
days_with_score_boost.append(day_to_test)
days_with_score_boost will be a list of datetime.date objects where the score increased by 10 or more from the day before.
In reponse to your comment, I'd make a measure that checks at save time whether the score boost has occured. However, I would get rid of auto_now_add in favor of writing in the save method.
from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist
class Score(models.Model):
user = models.ForeignKey(User)
score = models.PositiveIntegerField(default=0)
date = models.DateField(null=True,blank=True)
increased_ten_over_previous_day = models.BooleanField(null=True,blank=True)
def save(self, *args, **kwargs):
self.date = timezone.now().today()
try:
yesterday_score = Score.objects.get(date=self.date-timezone.timedelta(days=1)).score
self.increased_ten_over_previous_day = (yesterday_score * 10) <= self.score
except ObjectDoesNotExist: # called if Score.object.get returns no object; requires you only have one score per user per date
self.increased_ten_over_previous_day = False
super(self, Score).save(*args, **kwargs)
Then you could filter objects for a date_range where increased_ten_over_previous_day is True.

Subtract django.db.models.DateField from python's datetime.date to get age

In django, I want to get the age (in days) of an instance of a class. I tried doing that by subtracting its creation date field from today, but it does not seem to work properly. date.today() works fine, but DateField is giving me trouble. I looked at its source code and the django docs online for my version but I'm not sure how to manipulate it to perform the subtraction.
import datetime.date
from django.db import models
class MyItem(models.Model):
item_name = models.CharField(max_length = 30)
creation_date = models.DateField()
def age(self):
return date.today() - creation_date
my_first_item = MyItem(item_name = 'First', creation_date = '2005-11-01')
print my_first_item.age.days
Any input would be greatly appreciated!
Your problem is that you are trying to use a field instance outside of a model to represent a value.
models.DateField is a class which represents a database field with a type of "date". I suspect that you are looking to do one of the following:
Just do straight date math
Work with a value returned by a model
In the case of 1, you don't want to use Django's models at all. All you need and want is python's date and time handling classes. For your specific example all you need to use is a pair of date objects and you will end up with a timedelta object.
To do what you were trying to do in your example with the python standard classes, see the example below:
from datetime import date
birthday = date(year=2005, month=11, day=1)
today = date.today()
age = today - birthday
print age.days()
Here we instantiate a date with the birthdate values, we get a date with today's values, subtract them to get a timedelta, and finally print the number of days between the two dates.
In the case of 2, let's look at an example model:
class Person(models.Model):
name = models.CharField(max_length=100)
birthday = models.DateField()
Here we have a model where we've used models.CharField and models.DateField to describe a table in the database which contains a "varchar" column and a "date" column. When we fetch instances of this model using the ORM, Django handles converting whatever value the database returns to a native datatype. Now let's look at some code that figures out the age of an instance of a person:
from datetime import date
from myapp.models import Person
person = Person.objects.get(id=1)
age = date.today() - person.birthday
print age.days
Here you can see that we fetch an instance of the person model from the database and then we subtract their birthday from today. We're able to do this here, because when we access "person.birthday" Django is transforming whatever value the database returned into a python date object. This is the same type as the date object returned by "date.today()" so the "-" operator makes sense. The result of the subtraction operation is a timedelta object.

Custom model method in Django

I'm developing a web app in Django that manages chores on a reoccurring weekly basis. These are the models I've come up with so far. Chores need to be able to be assigned multiple weekdays and times. So the chore of laundry could be Sunday # 8:00 am and Wednesday # 5:30 pm. I first want to confirm the models below are the best way to represent this. Secondly, I'm a little confused about model relationships and custom model methods. Since these chores are on a reoccurring basis, I need to be able to check if there has been a CompletedEvent in this week. Since this is row level functionality, that would be a model method correct? Based on the models below, how would I check for this? It has me scratching my head.
models.py:
from django.db import models
from datetime import date
class ChoreManager(models.Manager):
def by_day(self, day_name):
return self.filter(scheduledday__day_name = day_name)
def today(self):
todays_day_name = date.today().strftime('%A')
return self.filter(scheduledday__day_name = todays_day_name)
class Chore(models.Model):
objects = ChoreManager()
name = models.CharField(max_length=50)
notes = models.TextField()
class Meta:
ordering = ['scheduledday__time']
class ScheduledDay(models.Model):
day_name = models.CharField(max_length=8)
time = models.TimeField()
chore = models.ForeignKey('Chore')
class CompletedEvent(models.Model):
date_completed = DateTimeField(auto_now_add=True)
chore = models.ForeignKey('Chore')
Then all you need to do is:
monday_of_week = some_date - datetime.timedetla(days=some_date.weekday())
end_of_week = date + datetime.timedelta(days=7)
chore = Chore.objects.get(name='The chore your looking for')
ScheduledDay.objects.filter(completed_date__gte=monday_of_week,
completed_date__lt=end_of_week,
chore=chore)
A neater (and faster) option is to use Bitmasks!
Think of the days of the week you want a chore to be repeated on as a binary number—a bit for each day. For example, if you wanted a chore repeated every Tuesday, Friday and Sunday then you would get the binary number 1010010 (or 82 in decimal):
S S F T W T M
1 0 1 0 0 1 0 = 1010010
Days are reversed for sake of illustration
And to check if a chore should be done today, simply get the number of that day and do an &:
from datetime import datetime as dt
if dt.today().weekday() & 0b1010100:
print("Do chores!")
Models
Your models.py would look a bit like this:
from django.contrib.auth.models import User
from django.db import models
from django.utils.functional import cached_property
class Chore(models.Model):
name = models.CharField(max_length=128)
notes = models.TextField()
class ChoreUser(models.Model):
chore_detail = models.ForeignKey('ChoreDetail')
user = models.ForeignKey('ChoreDetail')
completed_time = models.DateTimeField(null=True, blank=True)
class ChoreDetail(models.Model):
chore = models.ForeignKey('Chore')
chore_users = models.ManyToManyField('User', through=ChoreUser)
time = models.DateTimeField()
date_begin = models.DateField()
date_end = models.DateField()
schedule = models.IntegerField(help_text="Bitmask of Weekdays")
#cached_property
def happens_today(self):
return bool(dt.today().weekday() & self.weekly_schedule)
This schema has a M2M relationship between a User and a Chore's Schedule. So you can extend your idea, like record the duration of the chore (if you want to), or even have many users participating in the same chore.
And to answer your question, if you'd like to get the list of completed events this week, you could could put this in a Model Manager for ChoreUser:
from datetime import datetime as dt, timedelta
week_start = dt.today() - timedelta(days=dt.weekday())
week_end = week_start + timedelta(days=6)
chore_users = ChoreUser.objects.filter(completed_time__range=(week_start, week_end))
Now you have all the information you need in a single DB call:
user = chore_users[0].user
time = chore_users[0].chore_detail.time
name = chore_users[0].chore_detail.chore.name
happens_today = chore_users[0].chore_detail.happens_today
You could also get all the completed chores for a user easily:
some_user.choreuser_set.filter(completed_time__range=(week_start, week_end))